My Code:
import SwiftUI
struct Response: Codable {
var results: [Result]
}
struct Result: Codable {
var author: String
var text: String
}
struct ContentView: View {
@State private var results = [Result]()
var body: some View {
List(results, id: \.author) { item in // <--- This is where the problem is
VStack(alignment: .leading) {
Text(item.text)
Text(item.author)
}
}
.task{
await loadData()
}
}
func loadData() async{
guard let url = URL(string: "https://type.fit/api/quotes") else {
print("Invalid URL")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
results = decodedResponse.results
print(results)
}
} catch {
print("Invalid data")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Tutorial video: https://www.youtube.com/watch?v=MBCX1atOvdA&t=13s
The issue is that the tutorial I am following calls on a different api and uses information that my api does not provide as the id when displaying the results in a List. The tutorial uses an integer that is provided by the api as the index when placing the data into list format. I just am not sure what I should use for my id or if there is a better way I can get the information to display in a list
The api data that I am using is in the format & can be referenced at https://type.fit/api/quotes:
[
{
"text": "Genius is one percent inspiration and ninety-nine percent perspiration.",
"author": "Thomas Edison"
},
{
"text": "text",
"author": "author"
}, //and continues on for about 75,000 quotes
]
I have tried to set the id: .author and id: .text both of which throw no errors but I am getting nothing when I run the build
I would like to just end up with a single set of "text" and "author" information that I can display to a user
I have not tried much as I am relatively new to swiftui and xcode. I have a feeling this should be a simple fix and I may just be having a hard time wording my google searches to find a solution.
As previously mentioned, I would like to end up displaying a single pair of results containing 1 "text" and 1 "author"
CodePudding user response:
To get the data from the server you need to match your model structs to the json data.
In your case the json data is an array of Quotes. So you need to decode this, given
that the author
and possibly the text
, may be null.
Try this example code, works well for me:
// -- here
struct Quote: Identifiable, Codable {
let id = UUID() // <-- ensures a unique id
var author: String?
var text: String?
enum CodingKeys: String, CodingKey {
case author, text // <-- id is missing, it will not be decoded
}
}
struct ContentView: View {
@State var quotes = [Quote]() // <-- here
var body: some View {
VStack {
if quotes.count > 0 {
Text(quotes[0].text ?? "no text") // <-- here
Text(quotes[0].author ?? "no author").foregroundColor(.blue) // <-- here
}
}
// List(quotes) { quote in
// VStack(alignment: .leading) {
// Text(quote.text ?? "no text") // <-- here
// Text(quote.author ?? "no author").foregroundColor(.blue) // <-- here
// }
// }
.task{
await loadData()
}
}
func loadData() async {
guard let url = URL(string: "https://type.fit/api/quotes") else {
print("Invalid URL")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
quotes = try JSONDecoder().decode([Quote].self, from: data) // <-- here
} catch {
print(error) // <-- important
}
}
}