When trying to parse some data from a REST API endpoint in SwiftUI, I'm struggling to render out to a list correctly.
In the below example, my List requires the exact array position to render results, so it won't show anything without [0]
etc...
I'm used to solving this type of issue in JavaScript using a for loop and iterating over it to assign each array position to [i]
however in SwiftUI I'm struggling to do the same.
I think it's likely an issue with how I'm handling the nested JSON objects but here we go. I'm also confident it's probably an easy solution that I'm just blanking on here.
I've created a test endpoint that you can fetch from if you need to run it locally (this is in the below examples already).
To clarify what I'm looking to do here. I want to iterate over the array to reference (for example) Text(item.objects.title)
without having to specify which item in the array I'm looking for. Using a ForEach
doesn't work sadly because the List(viewModel.items, id: \.self) { item in }
is essentially replicating the ForEach
looping and doesn't work either.
Model
struct ObjectResponse: Hashable, Decodable {
let objects: [Object]
}
struct Object: Hashable, Decodable {
let slug: String
let title: String
let metadata: Metadata
}
struct Metadata: Hashable, Decodable {
let published: String
let url: String
let snippet: String
let read: Bool
}
ViewModel
class ContentViewModel: ObservableObject {
@Published var items: [ObjectResponse] = []
func fetchData() {
let api = "https://api.cosmicjs.com/v2/buckets/a5e294b0-55ee-11ec-942e-ef0a04148eb7/objects?pretty=true&query=%7B%22type%22%3A%22bookmarks%22%7D&read_key=fufvo5ceSy1W88afkchDIRjYrUIzErPw9YzcW2vQV1SxKqjNHo&limit=20&props=slug,title,content,metadata,"
guard let url = URL(string: api) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
if let data = data {
let result = try JSONDecoder().decode(ObjectResponse.self, from: data)
DispatchQueue.main.async {
self.items = [result]
/* Prints out everything nicely
result.objects.forEach {
print($0.title)
}
*/
}
} else {
print("No data")
}
} catch (let error) {
print(error)
}
}.resume()
}
}
Content View
struct ContentView: View {
@ObservedObject var viewModel = ContentViewModel()
var body: some View {
NavigationView {
List(viewModel.items, id: \.self) { item in
VStack(alignment: .leading) {
Text(item.objects[0].title) // Requires me to provide array position
}
}
.navigationTitle("Bookmarks")
}
.onAppear(perform: {
viewModel.fetchData()
})
}
}
Example JSON response
{
"objects": [
{
"slug": "the-navigation-bar-isnt-hidden-as-expected-in-swiftui",
"title": "The Navigation Bar Isn’t Hidden as Expected in SwiftUI",
"content": null,
"metadata": {
"published": "28 Nov 2021 at 16:30",
"url": "https://betterprogramming.pub/swiftui-navigationbar-is-not-really-hidden-as-you-expect-785ff0425c86",
"snippet": "",
"read": true
}
},
{
"slug": "hey-facebook-i-made-a-metaverse-27-years-ago",
"title": "Hey, Facebook, I Made a Metaverse 27 Years Ago",
"content": null,
"metadata": {
"published": "28 Nov 2021 at 21:39",
"url": "https://www.theatlantic.com/technology/archive/2021/10/facebook-metaverse-was-always-terrible/620546/",
"snippet": "Michel Baret / Gamma-Rapho / Getty In a booth at Ted’s Fish Fry, in Troy, New York, my friend Daniel Beck and I sketched out our plans for the metaverse. ",
"read": true
}
},
{
"slug": "when-big-tech-buys-small-tech-benedict-evans",
"title": "When big tech buys small tech — Benedict Evans",
"content": null,
"metadata": {
"published": "28 Nov 2021 at 21:39",
"url": "https://www.ben-evans.com/benedictevans/2021/11/12/when-big-tech-buys-small-tech",
"snippet": "Acquisitions are a big part of the discussion around competition in tech today, and a big set of questions. It’s easy to say that we wouldn’t let Amazon buy Zappos or Google buy DoubleClick again, but everyone’s favourite puzzle is Instagram. ",
"read": false
}
}
],
"total": 171,
"limit": 3
}