Search code examples
listswiftuiswiftui-navigationlink

SwiftUI Object details on list click


Building a crystal app. Displaying a list, showing details on click. Been looking into ObservableObject, Binding, etc.

Tried @State in CrystalView but got lost pretty quickly. What's the easiest way to pass data around views? Watched a few videos, still confused.

How do I pass crystals[key] into CrystalView()?

struct ContentView: View {
    @State private var crystals = [String:Crystal]()
    
    var body: some View {
        Text("Crystals").font(.largeTitle)
        NavigationView {
            List {
                ForEach(Array(crystals.keys), id:\.self) { key in
                    HStack {
                        NavigationLink(destination: CrystalView()) {
                            Text(key)
                        }
                    }
                }
            }.onAppear(perform:loadData)
        }
    }
    
    func loadData() {
        guard let url = URL(string: "https://lit-castle-74820.herokuapp.com/api/crystals") else { return }
        URLSession.shared.dataTask(with: url) { data, _, error in
            guard let data = data else { return }
            do {
                let decoded = try JSONDecoder().decode([String:Crystal].self, from: data)
                DispatchQueue.main.async {
                    print(decoded)
                    self.crystals = decoded
               }
            } catch let jsonError as NSError {
              print("JSON decode failed: \(jsonError)")
            }
        }.resume()
    }
}
struct Crystal: Codable, Identifiable {
    var id = UUID()
    let composition, formation, colour: String
    let metaphysical: [String]
}

struct CrystalView: View {
    var body: some View {
        Text("crystal")
    }
}

Solution

  • try this approach, works well for me:

    struct Crystal: Codable, Identifiable {
        var id = UUID()
        let composition, formation, colour: String
        let metaphysical: [String]
        
        // -- here, no `id`
        enum CodingKeys: String, CodingKey {
            case composition,formation,colour,metaphysical
        }
    }
    
    struct CrystalView: View {
        @State var crystal: Crystal?  // <-- here
        var body: some View {
            Text("\(crystal?.composition ?? "no data")")
        }
    }
    
    struct ContentView: View {
        @State private var crystals = [String:Crystal]()
        
        var body: some View {
            Text("Crystals").font(.largeTitle)
            NavigationView {
                List {
                    ForEach(Array(crystals.keys), id:\.self) { key in
                        HStack {
                            NavigationLink(destination: CrystalView(crystal: crystals[key])) {  // <-- here
                                Text(key)
                            }
                        }
                    }
                }.onAppear(perform: loadData)
            }
        }
        
        func loadData() {
            guard let url = URL(string: "https://lit-castle-74820.herokuapp.com/api/crystals") else { return }
            URLSession.shared.dataTask(with: url) { data, _, error in
                guard let data = data else { return }
                do {
                    let decoded = try JSONDecoder().decode([String:Crystal].self, from: data)
                    DispatchQueue.main.async {
                        print("decoded: \(decoded)")
                        self.crystals = decoded
                    }
                } catch let jsonError as NSError {
                    print("JSON decode failed: \(jsonError)")
                }
            }.resume()
        }
    }