Json data and read it on ContentView (SwitUI)

Hello guys, on parsing Json in SwiftUI question:

I parse data from remote Json and give it to output in console. In this step is very good.

How I can “insert” read this data field on my ContentView?.. I’m stuck on this…

Please help! or give me suggest tutorial, where is detailed about this.

I got error

Value of type 'UserResults' has no member 'email'

on Text(ppl?.email ?? "Email Placeholder") string

Thanks!

ContentView file:


import SwiftUI

struct ContentView: View {

    @State private var ppl: UserResults?

    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
            Text(ppl?.email ?? "Email Placeholder")
        }
        .onAppear {
            NetworkServiceWithCompletions.shared
                .fetchData { result in
                    switch result {
                    case .success(let usersData):
                        print("Data:\(usersData.results[0].email)")
                    case .failure(let failure):
                        print(failure.localizedDescription)
                    }
              }
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

User file:

import Foundation

struct UserResults: Decodable {
    let results: [User]

    struct User: Decodable {
        var gender: String
        var name: Name
        var email: String
        var picture: Picture

        struct Name: Decodable {
            var title: String
            var first: String
            var last: String
        }

        struct Picture: Decodable {
            var large: String

        }

    }

}

NetworkServiceWithCompletions file:

import Foundation

class NetworkServiceWithCompletions: ObservableObject {

    static let shared = NetworkServiceWithCompletions(); private init() { }

    private func createURL() -> URL? {
        let tunnel = "https://"
        let server = "randomuser.me"
        let endpoint = "/api"
        let getParams = ""
        let urlStr = tunnel + server + endpoint + getParams

        let url = URL(string: urlStr)
        return url
    }

    func fetchData(completion: @escaping (Result<UserResults, Error>) -> ()) {
        guard let url = createURL() else {
            completion(.failure(NetworkingError.badUrl))
            return
        }

            URLSession.shared.dataTask(with: url) { data, response, error in
                guard let data else {
                    if let error {
                        completion(.failure(error))
                    }
                    return
                    }

                 let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .convertFromSnakeCase
                do {
                    let usersData = try decoder.decode(UserResults.self, from: data)
                    completion(.success(usersData))
                } catch {
                    completion(.failure(NetworkingError.invalidData))
                }

                }.resume()
            }
        }

    enum NetworkingError: Error {
        case badUrl, badRewuest, badResponse, invalidData
    }

You are getting the error because UserResults contains an array of User objects. User has an email property, not UserResults. To show an email address in the Text view, you have to get a user from the array and show that user’s email address. You also need to create a list to show each user’s email address. The code you showed for the content view will show only one user, which is most likely not what you want.

Why are you nesting the User struct inside the UserDefaults struct and nesting the Name and Picture structs inside User? Doing that is going to make it more difficult to access the fields in the structs. Change the code in the user file to the following:

struct UserResults: Decodable {
    let results: [User]
}

struct User: Decodable {
    var gender: String
    var name: Name
    var email: String
    var picture: Picture
}

struct Name: Decodable {
    var title: String
    var first: String
    var last: String
}

struct Picture: Decodable {
    var large: String
}

There could be other problems with your code, but what I’ve written should give you a start on getting your code to work.

1 Like

Because Json structure has this nesting…

https://randomuser.me/api

If I remove nesting, I can’t parse this Json correctly…
Any help appreciate…

Read the first paragraph of my previous answer again.

I also recommend starting by creating a UserResult object in code and populating a SwiftUI list with that data. When you get that working, you can move on to populating the list with your JSON data.

Regarding a tutorial, a query for pppulate SwiftUI list with JSON data in a search engine brought up multiple articles including the following:

How to Fetch JSON Data from APIs in SwiftUI

I read you solution many times, and make changes:

user file:

import Foundation

struct UserResults: Decodable {
    let results: [User]
}

struct User: Decodable {
    var gender: String
    var name: Name
    var email: String
    var picture: Picture
}

struct Name: Decodable {
    var title: String
    var first: String
    var last: String
}

struct Picture: Decodable {
    var large: String
}

ContentView file – I make the list! :slight_smile: :

import SwiftUI

struct ContentView: View {
    
    @State private var ppl: User?
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
            
            List(/*@START_MENU_TOKEN@*/0 ..< 5/*@END_MENU_TOKEN@*/) { item in
                Text(ppl?.email ?? "Email placeholder")
            }
        }
        .onAppear {
            NetworkServiceWithCompletions.shared
                .fetchData { result in
                    switch result {
                    case .success(let usersData):
                        print("Data:\(usersData.results[0].email)")
                    case .failure(let failure):
                        print(failure.localizedDescription)
                    }
              }
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

file NetworkServiceWithCompletions.swift no changes

I got only Placeholder, not really email (please see screen):
Screenshot 2023-10-21 at 20.39.15

Problem not resolved…

But thanks for tutorial! I’ll check it soon

Where do you set the value of ppl?

What are you doing with the following code?

List(/*@START_MENU_TOKEN@*/0 ..< 5/*@END_MENU_TOKEN@*/) { item in
    Text(ppl?.email ?? "Email placeholder")
}

What are START_MENU_TOKEN and END_MENU_TOKEN?

In the best case scenario your list is going to print the ppl user’s email address every time. But ppl is nil so you get the email placeholder.

You need to pass an array to the list. Go through each item in the list and create an item for it. You have a UserResults struct. Create an instance of that struct. Pass the array of users in the UserResults struct to the list. Go through each user in the array and display its email address in a Text label. The code will look something like the following:

@State var users: UserResults

List {
    ForEach(users.results, id: .\self) { user in
        Text(user.email)
    }
}

You sound like you’re new to iOS development. I recommend going through a course like Hacking with Swift’s 100 Days of SwiftUI course to learn the fundamentals of SwiftUI development. The course is free and geared towards new developers. After finishing a course like the 100 Days of SwiftUI, you will have an easier time with what you’re trying to do in this project.

Ok thanks for the explanation and links.

Copy/paste from xCode, fixed:

            List(0 ..< 5) { item in
                Text(ppl?.email ?? "Email placeholder")
            }

If you saw API above: https://randomuser.me/api, it have only one user, no List required… ForEach also… (You told me about List in yours “First Paragraph”)

And yes, I’m new here, that’s why I’m asking questions. If I were an experienced user, I would answer them. And I think I would do it better than some :wink:

I didn’t know that the Codeco forum was only for “Guru” level users…

Sorry for wasting your precious time on me.

Hi @erbrugger and welcome to Forums! All skill levels are encouraged to participate.

Thought I’d join in and provide another point of view on what I see in the code:

When parsing JSON, the structures that will decode must reflect that of the data. Seems like you guys have figured most of that out. But seems like you are accessing the email property prior to the array, as if the property (email) was part of the base struct. I haven’t run the code myself (working RN) but maybe this:

            List(0 ..< 5) { item in
                Text(ppl?.email ?? "Email placeholder")
            }

should be:

            List(0 ..< 5) { item in
                Text(item?.email ?? "Email placeholder")
            }

Let us know if you are still having troubles and I’ll run the code tonight and share fixes, sometimes and example is a good explanation.

BTW, the links that @szymczyk shared are really good, we all start somewhere.

Hello @robertomachorro and thanks for response.

I can’t understand why I need List here, if API content is only one user (but wrapped in array, but only one)? Can I make it without List or ForEach, just like Text or something?

And I correct mistake but get another errors…

If you can give me working code, maybe I understand where is trouble here…

Thanks again!

About the links, they are great yes, and I’m learning from hackingwithswift and many other resources…

I can’t understand why I need List here, if API content is only one user (but wrapped in array, but only one)?

The reason why is that while you know you are only getting one user, the format for the structure would allow multiple users (an array). Neither Swift nor you can know for sure that will be the case forever. So you have to be able to handle that other scenario, even if it never happens.

Can I make it without Lis t or ForEach , just like Text or something?

You could, something like:

Text(item?.first?.email ?? "No email")

Sample code for you is still on my todo list, it’s just been an extra long list this week… stay tuned!

To put this into code, consider the following example below. Every time a button is pressed, a fetch is performed, then the email is returned to fill a Text field.

struct ContentView: View {
	@State private var email = "-"

	var body: some View {
		VStack {
			Text(email)
			Button("Fetch User") {
				Task {
					email = await fetchUser()
				}
			}
		}
	}

	func fetchUser() async -> String {
		let url = URL(string: "https://randomuser.me/api")!
		if let (data, response) = try? await URLSession.shared.data(from: url),
			let httpResponse = response as? HTTPURLResponse,
			httpResponse.statusCode == 200,
			let users = try? JSONDecoder().decode(UserResults.self, from: data) {
			return users.results.first?.email ?? "No Email"
		}
		return "Failed fetch user."
	}
}

Although I’d probably refactor this into more re-usable code:

struct ContentView: View {
	@State private var email = "-"

	var body: some View {
		VStack {
			Text(email)
			Button("Fetch User") {
				Task {
					email = await fetchUser()?.email ?? "No Email"
				}
			}
		}
	}

	func fetchUser() async -> UserResults.User? {
		let url = URL(string: "https://randomuser.me/api")!
		if let (data, response) = try? await URLSession.shared.data(from: url),
			let httpResponse = response as? HTTPURLResponse,
			httpResponse.statusCode == 200,
			let users = try? JSONDecoder().decode(UserResults.self, from: data) {
			return users.results.first
		}
		return nil
	}
}

@robertomachorro thank you,
It’s clear that there’s something wrong with my code…
(need correction and refactor)

thanks again for the example! I’ll definitely look into it!

This topic was automatically closed after 166 days. New replies are no longer allowed.