Home > Net >  Trouble accessing elements inside json throughout app
Trouble accessing elements inside json throughout app

Time:01-24

Hello im quite new to SwiftUI and having some difficulties. I am trying to access information from my decoded json file and keep getting an error ->

Cannot convert value of type [String : Village] to expected argument type Binding<C>

and also the following error ->

Cannot convert value of type Binding<Subject> to expected argument type 'String'.

Below is my code

my struct along with a computed property using string interpolation to match image name inside assets.

struct Village: Identifiable, Codable {
    let id: Int
    let japName: String
    let description: String

    var image: String {
        "village\(id)"
    }

}

the following is Json example

 {
    "leaf": {
        "id": 1,
        "japName": "木ノ葉隠れの里, Konohagakure no Sato",
        "description": "Konohagakure (木ノ葉隠れの里, Konohagakure no Sato, Village Hidden in the Leaves or Hidden Leaf Village, literally meaning: Village Hidden by Tree Leaves) is the hidden village of the Land of Fire. As the village of one of the Five Great Shinobi Countries, Konohagakure has a Kage as its leader known as the Hokage, of which there have been seven in its history. Konoha resides deep within a forest at the base of a mountain known as the Hokage Rock, which has the faces of all those who have taken the office of Hokage engraved on it. It is surrounded on all sides by enormous walls.[2] While generally seen as the most powerful of the ninja villages, Konoha has enjoyed many years of relative peace and stability.The standard attire for Konoha shinobi consists of blue or black shirts which may or may not have swirl patterns on the shoulders, along with matching coloured pants under a green flak jacket which also has a red swirl on the back, and pockets on the chest area. They also tend to wrap bandages around their legs. Many Konohagakure ninja use Fire Release techniques."
}, 
     "sand": {
         "id": 2,
        "japName": "砂隠れの里, Sunagakure no Sato",
        "description": "Village Hidden by Sand) is the hidden village of the Land of Wind. As the village of one of the Five Great Shinobi Countries, Sunagakure has a Kage as its leader known as the Kazekage, of which there have been five in its history. Being surrounded by desert offers the village a natural deterrent against invasion, as few foreign powers would be willing to endure the sandstorms and water scarcity common to Suna. Sunagakure itself lies in a fortified valley behind cliffs of rock, with passage in and out of the village restricted to a single cleft between two cliff faces, making the area very hard to attack from the ground. The buildings inside seem to be made of clay or stucco, which would help to keep the buildings cool.The standard attire for Suna shinobi consists of blue or black uniforms underneath flak jackets of various shades of beige to better blend with their natural environment and include shoulders guards. Headpieces consisting of a head-wrap and cloth neck-covers to protect against the harsh weather are popular with Suna shinobi. Many Sunagakure ninja use Wind Release techniques, which they may or may not perform with fans.Shinobi from this village believe that the accomplishment of a mission predominates the lives of the ninja trying to accomplish it. As such, they ruthlessly pursue victory in battle, and even if failure is inevitable they will still try to achieve any success against their enemy that they can. In Gaara Hiden, it has been noted that Sunagakure has been made up of various families, instead of clans."
},
    "stone": {
            "id": 3,
            "japName": "岩隠れの里, Iwagakure no Sato",
            "description": "Hidden Stone Village, literally meaning: Village Hidden by Rocks) is the hidden village of the Land of Earth. As the village of one of the Five Great Shinobi Countries, Iwagakure has a Kage as its leader known as the Tsuchikage, of which there have been four in its history. The rocky mountain ranges that surround the village provide a natural stronghold that it is very proud of. The village's infrastructure is built from much of the surrounding rock and stone, shaped into tower-like structures that are inter-connected by a network of bridges. The Tsuchikage's residence seems to be the tallest structure with a cone-shaped roof and bears the kanji for Earth (土, Tsuchi) on it. The standard attire for Iwa shinobi consists of red outfit — which can either have one, or both sleeves — a lapel which is usually found on the side without a sleeve and a brown flak jacket — which also may, or may not have a pouch attached to it. They also tend to wear mesh armour around their ankles. Many Iwagakure ninja use Earth Release techniques. Iwagakure is well-known for the rock-hard attitude of its shinobi; as soon as the Tsuchikage gives an order, the Iwa-nin follow it without hesitation, even if it means death."
},
   "cloud": {
       "id": 4,
        "japName": "雲隠れの里, Kumogakure no Sato",
        "description": "Hidden Cloud Village, literally meaning: Village Hidden by Clouds) is the hidden village of the Land of Lightning and was founded by the First Raikage. As the village of one of the Five Great Shinobi Countries, Kumogakure has a Kage as its leader known as the Raikage, of which there have been five in its history. The village is located in a range of tall mountains, and is literally hidden in the clouds. The Raikage works in a large blue structure built into the tallest mountain.The standard attire for Kumo shinobi consists of a long, grey top which gathers just at the waist to give a sash-like appearance, with a matching coloured bottom. Over this they wear white, one-strapped flak jackets and arm as well as shin-guards. Many Kumogakure ninja use Lightning Release techniques."
    }
}

the following is extension to decode

extension Bundle { 

    func decode(_ file: String) -> [String: Village] { 

    guard let url = self.url(forResource: file, withExtension: nil) else { fatalError("Failed to locate (file) in bundle.") }

    guard let data = try? Data(contentsOf: url) else {
        fatalError("Failed to load \(file) from bundle.")
    }

    let decoder = JSONDecoder()

    guard let loaded = try? decoder.decode([String: Village].self, from: data) else {
        fatalError("Failed to decode \(file) from bundle.")
    }

    return loaded
    }
}

the following is my contentView code... I am creating a horizontal scroll view with multiple NavigationLink's with Labels. The Navigation link will take you to a view that shows my json data. I get most of my errors when I'm accessing village inside the ForEach, and also when trying to access my images in assets using the computed property inside the label.

struct ContentView: View {
    
    let village: [String: Village] = Bundle.main.decode("Villages.json")
 
    let rows = [
        GridItem(.fixed(20))]
  
    var body: some View {
        NavigationView {
            ScrollView(.horizontal) {
                LazyHGrid(rows: rows) {
                    ForEach(village) { village in
                        NavigationLink {
                            VillageView(village: village)
                        } label: {
                            Image(village.image)
                                .resizable()
                                .scaledToFill()
                                .frame(width: 200, height: 200)
                                .clipShape(Circle())
                                .overlay {
                                    Circle()
                                        .stroke(Color.orange, lineWidth: 4)
                                        
                                        
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

I am trying to access the json data to display the information when the navigationLink is clicked. Im using a ForEach to iterate over my json Data so when a specific link is clicked it will only show that specific information. Also I was trying to use my computed property inside the label to access my images in assets. Each image is title village1, village2, village3, village4. As you can see above I used string interpolation for this inside my struct Village.

Again im quite new to swift so please take it easy on me haha. Thanks in advance

CodePudding user response:

The issue you are facing is your [String:Village] dictionary. The errors you are seeing are misleading. This can be solved by using a [Village] array:

extension Bundle {
    
    func decode(_ file: String) -> [Village] {
        
        guard let url = self.url(forResource: file, withExtension: nil) else { fatalError("Failed to locate (file) in bundle.") }
        
        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file) from bundle.")
        }
        
        let decoder = JSONDecoder()
        
        guard let loaded = try? decoder.decode([String: Village].self, from: data) else {
            fatalError("Failed to decode \(file) from bundle.")
        }
        
        return loaded.map{$0.value}
    }
}

and

struct ContentView: View {
    
    var villages: [Village] = Bundle.main.decode("Villages.json")
    
    let rows = [
        GridItem(.fixed(20))]
    
    var body: some View {
        NavigationView {
            ScrollView(.horizontal) {
                LazyHGrid(rows: rows) {
                    ForEach(villages) { village in
                        NavigationLink {
                            //                            VillageView(village: village)
                        } label: {
                            Image(village.image)
                                .resizable()
                                .scaledToFill()
                                .frame(width: 200, height: 200)
                                .clipShape(Circle())
                                .overlay {
                                    Circle()
                                        .stroke(Color.orange, lineWidth: 4)
                                    
                                    
                                }
                        }
                    }
                }
            }
        }
    }
}

CodePudding user response:

I would recommend using an ordered Dictionary. For implementation, please see: https://stackoverflow.com/a/68023633/14251720

I have tried this implementation with your code and it works. Just note that you need to import Collections separately as a package, and use the keys for iteration like below:

ForEach(village.keys, id: \.self) { key in ...}
  • Related