Home > Software design >  SwiftUI Object details on list click
SwiftUI Object details on list click

Time:06-23

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")
    }
}

CodePudding user response:

Something like this:

struct ContentView: View {
    @State private var crystals: [Crystal] = []
    
    var body: some View {
        NavigationView {
            List {
                ForEach(crystals) { crystal in
                    NavigationLink(destination: CrystalView(crystal: crystal)) {
                        Text(crystal.name)
                    }        
                }
            }
            .navigationTitle("Crystals")

            // initial detail view
            Text("Select a crystal")
        }
        .task {
            crystals = try? await fetchCrystals()
        }
    }
    
    func fetchCrystals() async throws -> [Crystal] {
        let (data, _) = try await URLSession.shared.data(from: "https://lit-castle-74820.herokuapp.com/api/crystals")
        let decoder = JSONDecoder()
        return try decoder.decode([Crystal].self, from: data) // you might want to convert this downloaded struct into a more suitable struct for the app.
    }
}

CodePudding user response:

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()
    }
}
  • Related