在 SwiftUI 中迭代 Codable 数组

Iterating over array of Codable in SwiftUI

作为 的后续行动,我现在想在 SwiftUI 中循环访问 Codable 结构数组,并在我的 ContentView{} 中将它们呈现为文本或列表项。

我尝试在 .task 部分实现一个变量 geoDataArray,然后在我的 ContentView 部分用 ForEach 对其进行迭代,但收到了很多错误关于类型和展开值。

感谢任何帮助!我还是 SwiftUI 的新手。

下面是我的代码:

struct GeoService: Codable {
    var status: String
    var results: [GeoResult]
}

struct GeoResult: Codable {
    
    struct Geometry: Codable {
        
        struct Location: Codable {
            
            let lat: Float
            let lng: Float
            
            init() {
                lat = 32
                lng = 30
            }
        }
        let location: Location
    }
    let formatted_address: String
    let geometry: Geometry
}



struct ContentView: View {

//    @State private var results: Any ?????????
    
    var body: some View {
        NavigationView {
            Text("Test")
                .navigationTitle("Quotes")
                .task {
                    await handleData()
                }
        }
        
    }
    
    func handleData() async {
        let geoResult="""
        {
          "results": [
            {
              "formatted_address": "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA",
              "geometry": {
                "location": {
                  "lat": 37.4224764,
                  "lng": -122.0842499
                }
              }
            },
            {
              "formatted_address": "Test addresss",
              "geometry": {
                "location": {
                  "lat": 120.32132145,
                  "lng": -43.90235469
                }
              }
            }
          ],
          "status": "OK"
        }
        """.data(using: .utf8)!
        
        let decoder = JSONDecoder()
        print("executing handleData()")
        do {
            let obj = try decoder.decode(GeoService.self, from: geoResult)
            for result in obj.results {
                print("Address: \(result.formatted_address)")
                print("Lat/long: (\(result.geometry.location.lat), \(result.geometry.location.lng))")
            }
        } catch {
            print("Did not work :(")
        }
    }
}

您的代码在打印到控制台时工作正常,但是 ForEach 要求 GeoResult 符合 Identifiable(首选)或至少 Hashable.鉴于您没有在代码中包含 属性 id,让我们让该结构符合 Hashable.

所以,假设每个 GeoResult 是不同的,因为 formatted_address 永远不一样(你必须检查它是否正确),你可以添加两个确保一致性的功能。您将获得以下内容:

struct GeoResult: Codable, Hashable {    // <- Conform to Hashable
    
    // Differentiating
    static func == (lhs: GeoResult, rhs: GeoResult) -> Bool {
        lhs.formatted_address == rhs.formatted_address
    }

    // Hashing
    func hash(into hasher: inout Hasher) {
        hasher.combine(formatted_address)
    }
    
    
    struct Geometry: Codable {
        
        struct Location: Codable {
            
            let lat: Float
            let lng: Float
            
            init() {
                lat = 32
                lng = 30
            }
        }
        let location: Location
    }
    let formatted_address: String
    let geometry: Geometry
}

在视图中,添加一个 GeoResult 数组,这将是要迭代的 @State 变量。将 .task() 修饰符放在最外面的视图上。

    // This is the list
    @State private var geoArray: [GeoResult] = []
    
    var body: some View {
        NavigationView {
            VStack {
                
                // GeoResult is not Identifiable, so it is necessary to include id: \.self
                ForEach(geoArray, id: \.self) { result in
                    NavigationLink {
                        Text("Lat/long: (\(result.geometry.location.lat), \(result.geometry.location.lng))")
                    } label: {
                        Text("Address: \(result.formatted_address)")
                    }
                }
                .navigationTitle("Quotes")
            }
        }
        
        // Attach the task to the outermost view, in this case the NavigationView
        .task {
            await handleData()
        }
    }

最后,解码后更改函数中的 @State 变量:

    func handleData() async {

        // ...
        
        let decoder = JSONDecoder()
        do {
            let obj = try decoder.decode(GeoService.self, from: geoResult)
            
            // Add this
            geoArray = obj.results
        } catch {
            print("Did not work :(\n\(error)")
        }
    }