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.
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:
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! :
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):
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.
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
I didn’t know that the Codeco forum was only for “Guru” level users…
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:
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?
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
}
}