UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Day 60 and 61 - FriendFace - need help getting data to fetch

Forums > 100 Days of SwiftUI

Hello, trying to figure out why my initial fetch won't happen. It was working until I decided to hold an array of my User structs as a property in a class. In the line if let decodedUsers = try?..., while I once had it putting the data into [User].self, that doesn't work, and doesn't make sense to me anymore. However, if I change it to the name of my class, which is Users, with the property it's going to the ObserveObject users, the error says "Instance method 'decode(:from:)' requires that 'Users' conform to 'Decodable" and also all of a sudden, another error says that DispatchQueue.main.aysnc is "ambigous without additional context." I thought the Decodable conformance would happen, because the User struct is nested inside the Users class, and does have Codable conformance. Here's my ContentView with the relevant function and my class + structs. Any help would be greatly appreciated!

  struct ContentView: View {
    @ObservedObject var users: Users = Users()
    @Environment(\.managedObjectContext) var moc

      var body: some View {
        NavigationView {
          List {
            ForEach(// don't know what to put here anymore) { user in
              NavigationLink(destination: DetailView(users: self.users, user: user)) {
                UserCellView(user: user)
              }
            }
          }
          .navigationTitle("Users")
        }
      }

    func loadData() {
      guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {
        print("Invalid URL")
        return
      }
      let request = URLRequest(url: url)
      URLSession.shared.dataTask(with: request) { data, response, error in
        if let data = data {
          if let decodedUsers = try? JSONDecoder().decode(Users.self, from: data) {
            DispatchQueue.main.async {
              self.users = decodedUsers
            }
            return
          }
        }
        print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
      }.resume()
    }
    }

  class Users: ObservableObject {
@Published var items: [User]

init() {
  items = [User]()
}

func findUser(byName name: String) -> User? {
  if let user = items.first(where: { $0.name == name }) {
    return user
  }

  return items.first
}

struct User: Codable, Identifiable {
struct Friend: Codable {
    let id: String
    let name: String
}
  let id: String
  let isActive: Bool
  let name: String
  let age: Int
  let company: String
  let email: String
  let address: String
  let about: String
 // let registered: String
  let tags: [String]
  let friends: [Friend]
}
}

3      

I'm unclear why you want a User struct inside a Users struct (not a class as written). To me, that's deeply confusing. Usually, users would refer to an array (or set) of the User objects.

You'd need to have Users as a whole also conform to codable in the case you've given, though. Here's a link about Codable conformance.

3      

Hey, thanks so much for your response! I modeled this after challenge #3 from the cupcake app, which worked well for that, so I thought it might work well here too. That involved nesting a struct called OrderInstance inside a class, whose only property was @Published var detail: OrderIntance, and then had an initializer of course. The class (called Order) didn't have its own Codable conformance (just ObservableObject), which I thought was the point of Paul giving that challenge: to show that the legwork of making a @Published property conform to Codable wasn't necessary because the property's content conformed. The nesting seemed particularly important, but now I'm not sure. From there, I was able to encode the OrderInstance and decode it back. I'm a little confused by your first sentence, because I only have a Users class, not a struct. Are you saying that the naming is confusing? As for the class also needing to conform to Codable, I'm struggling to look at these two examples and see the best course, or why this one isn't working as well as the first one.

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Hi @jessilinden,

I thought the Decodable conformance would happen, because the User struct is nested inside the Users class, and does have Codable conformance.

I'm not sure about the asyn error, but I believe what you're running into with the Users class Codable conformance is talked about here:

https://www.hackingwithswift.com/books/ios-swiftui/adding-codable-conformance-for-published-properties

Just because the structs in the class are codable, doesn't make the class codable. And he goes through the exercise of making it confrom to codable by using CodingKeys. But it seems easier - and cleaner - to just separate out the User struct in another file.

Also, you have" @Published var items: [User]

I'm wondering why it's [User] and not [User]() ? Does that make a difference?

3      

Thank you both for your responses! Even though I did add Codable conformance to that class and the 2 errors from loadData() went away, I decided to start fresh, keeping it simple to start. I made a file with my User and Friend structs: <br>


struct User: Identifiable, Codable {
  var id: String
  var isActive: Bool
  var name: String
  var age: Int
  var company: String
  var email: String
  var about: String
  var registered: Date
  var tags: [String]
  var friends: [Friend]
}
struct Friend: Identifiable, Codable {
  var id: String
  var name: String
}

and ContentView, which has the loadData() method. The code compiles, but the fetch fails. Spot anything wrong with it? Based on some print statements I experimented with, I can tell that it's the JSONDecoder().decode line that isn't working. The print statement went right below that, and I only got the Fetch Failed: Unknown Error statement. Perhaps worth mentioning that when I did the tutorial with the Taylor Swift albums that were supposed to get decoded from iTunes, same problem: compiles, but no decoding. When I did the Cupcake Corner app, it involved encoding first, so the decoding worked.

struct ContentView: View {
  @State var results: [User] = [User]()

    var body: some View {
      NavigationView {
        List(results, id: \.id) { user in
            VStack {
              Text(user.name)
                .font(.title2)
                .foregroundColor(.primary)
              Text(user.email)
                .font(.subheadline)
                .foregroundColor(.secondary)
            }
        }
      }
      .navigationBarTitle("FriendFace")
      .onAppear(perform: loadData)
    }

  func loadData() {
    guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {
      print("Invalid URL")
      return
    }
    let request = URLRequest(url: url)
    URLSession.shared.dataTask(with: request) { data, response, error in
      if let data = data {
        if let decodedUsers = try? JSONDecoder().decode([User].self, from: data) {
          DispatchQueue.main.async {
            self.results = decodedUsers
          }
          return
        }
      }
      print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
    }.resume()
  }
}

3      

Hi again,

I copied your code and ran it. (I'm just starting this project too!). I got the same ' Fetch Failed: Unknown Error'.

I then chaned the registered type from Date to String - and it worked. I haven't looked into why the Date didn't work, or even what the format yet is in the JSON. (haven't gotten very far in the project :) ).

I also tried using Date? for registered, and got teh same Fetch Failed Error.

My plan is to read the Date as a string, do whatever conversion/formatting I need after I decode the JSON.

3      

In reviewing my notes, I see the lesson mentions you need to apply a date formatter if you want that field. I guess another option is to just ignore that field, if you don't want to show the date. (But if you do keep in the Date and format it, it should be a Date Optional, I think, too. Just in case).

3      

If you want to see my reply on Day 60: error 403 when trying to fetch friendface.json thread it the same question.

The date is.iso8601 if you add this it will work after URLSession.shared.dataTask

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

5      

Thanks, Nigel! And then ... the formatter I mentioned above would be used if we used the dates in our app and wanted them formatted, I assume?

3      

Yes because the type is a Date so you can formatter like Date(). In my example just used

Text("Joined: \(user.registered, style: .date)")

3      

Hey, thanks to you both! Got the app functioning now and that date solution is so succinct. Just out of curiosity, do you have a suggestion for how you might handle an optional Date as the type for the property registered? I just experimented with something like this, inspired by some of Paul's other code, but obviously I gave a String as the default value, which won't work here since it's a Date.


extension User {
  public var wrappedDate: Date {
    get {
      self.registered ?? "Registered date unknown"
    }
    set {
      registered = newValue
    }
  }
}

I'd also welcome anyone's insight on first(where:) to connect the name of the Friend to the rest of the details for THEIR User info. Equatable as another option? I see where we did first(where:) in Moonshot, but it's in an initializer... Assuming you could put it in a function as well, but a little lost on the smoothest approach here.

3      

Facing same issue! If I add an expense and then tap Edit the red circles do not show up, but Edit does change to Done in the NavBar. Then if I swipe on any item to expose the row's delete option on the far right side, and then either commit the delete or cancel out of it, then tap edit the delete circles show up.

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.