As a follow-up to this question, I now want to iterate through an array of Codable structs in SwiftUI and render them in my ContentView{} as Text or List items.
I have tried implementing a variable, geoDataArray
, in the .task
section then iterating over it with a ForEach
in my ContentView
but received a lot of errors about types and unwrapping values.
Any help is appreciated! I am still new to SwiftUI.
Below is my code:
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 :(")
}
}
}
CodePudding user response:
Your code works fine the way it is for printing to the console, but ForEach
requires that GeoResult
conforms to either Identifiable
(preferred) or at least Hashable
. Given that you didn't include the property id
in your code, let's have that struct conforming to Hashable
.
So, assuming that each GeoResult
is different because formatted_address
is never the same (you must check if that's true), you can add two functions to ensure conformance. You will get the following:
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
}
In the view, add an array of GeoResult
, that will be the @State
variable to iterate over. Place the .task()
modifier on the outermost view.
// 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()
}
}
Finally, change the @State
variable in your function, after decoding:
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)")
}
}