TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: Day 61: FriendFace Core Data added but now NavigationLink refuses to work?

Forums > 100 Days of SwiftUI

Hi,

I have managed to add the core data into the application with some help. Everything that end seems to be working just fine, but I am now getting an error when I run the app & try to click a user to go into the detail view:

Thread 1: "-[__NSSetI _fastCStringContents:]: unrecognized selector sent to instance 0x600001b00020" 

I truly am not sure why this is, as the link looks correct & I am a tad lost as to where it went wrong? The entities are setup with contrained id's. Friends are set through the relationship (to many) & I have checked all the attributes are spot on.

Here is my content view:

import SwiftUI
import CoreData

struct ContentView: View {
    // Core Data
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(sortDescriptors: [SortDescriptor(\.name)]) var users: FetchedResults<CachedUser>

    // Store the result from JSON file
    @State private var usersData = [UserData]()

    func updateCache(with downloadedUsers: [UserData]) {
        for user in downloadedUsers {
            let cachedUser = CachedUser(context: moc)

            cachedUser.id = user.id
            cachedUser.isActive = user.isActive
            cachedUser.age = Int16(user.age)
            cachedUser.email = user.email
            cachedUser.address = user.address
            cachedUser.name = user.name
            cachedUser.about = user.about
            cachedUser.registered = user.registered
            cachedUser.tags = user.tags.joined(separator: ",")

            for friend in user.friends {
                let cachedFriend = CachedFriend(context: moc)
                cachedFriend.id = friend.id
                cachedFriend.name = friend.name
            }
        }
        try? moc.save()
    }

    // JSON
    func loadData() async {
        // Don't re-fetch data if we already have it
        guard users.isEmpty else { return }

        do {
            // Create the URL
            let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json")!

            // Fetch the data
            let (data, _) = try await URLSession.shared.data(from: url)

            // Convert the data
            let decoder = JSONDecoder()
            decoder.dateDecodingStrategy = .iso8601

            let users = try decoder.decode([UserData].self, from: data)
            await MainActor.run {
                updateCache(with: users)
            }

        } catch {
            print("Download Failed \(error.localizedDescription)")
        }
    }

    var body: some View {

        NavigationView {
            List(users) { user in
                NavigationLink {
                    UserDetailView(user: user)
                } label: {
                    HStack {
                        Text(user.wrappedName)
                        Spacer()
                        Circle()
                            .fill(user.isActive ? .green : .red)
                            .frame(width: 15)
                    }
                }
            }
            .navigationTitle("FriendFace")
            .task {
                await loadData()

            }

        }

    }
}

My user detail view:

import SwiftUI
import CoreData

struct UserDetailView: View {

    let user: CachedUser

    var body: some View {
        List {
            Section {
                VStack {
                    HStack {
                        Text(user.wrappedName)
                        Spacer()
                        Text("Age: \(String(user.age))")
                    }
                    .padding()
                    Text("Joined: \(user.wrappedRegistered, style: .date)")
                }
            } header: {
                Text("User")
            }

            Section {
                VStack(alignment: .leading) {
                    Text("Address: \(user.wrappedAddress)")
                        .padding(.vertical)
                    Text(user.wrappedCompany)
                        .padding(.vertical)
                    Text("Email: \(user.wrappedEmail)")
                        .padding(.vertical)
                }
            } header: {
                Text("Contact")
            }

            Section {
                Text(user.wrappedAbout)

            } header: {
                Text("About")
            }

            Section {
                ForEach(user.friendsArray) { friend in
                    Text(friend.wrappedFriendName)
                }
            } header: {
                Text("Friends")
            }

        }
        .listStyle(.grouped)
        .navigationTitle(user.wrappedName)
        .navigationBarTitleDisplayMode(.inline)
    }
}

& my cached user file as I am guessing this may be of inportance.

import Foundation
import CoreData

extension CachedUser {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<CachedUser> {
        return NSFetchRequest<CachedUser>(entityName: "CachedUser")
    }

    @NSManaged public var id: UUID?
    @NSManaged public var name: String?
    @NSManaged public var age: Int16
    @NSManaged public var address: String?
    @NSManaged public var email: String?
    @NSManaged public var company: String?
    @NSManaged public var isActive: Bool
    @NSManaged public var friends: String?
    @NSManaged public var tags: String?
    @NSManaged public var about: String?
    @NSManaged public var registered: Date?

    // Wrapped variables
    public var wrappedName: String {
        name ?? "Unknown user"
    }
    public var wrappedAddress: String {
        address ?? "Unknown address"
    }
    public var wrappedEmail: String {
        email ?? "Unknown email address"
    }
    public var wrappedCompany: String {
        company ?? "Unknown company"
    }
    public var wrappedAbout: String {
        about ?? "I am secretive"
    }
    public var wrappedRegistered: Date {
        registered ?? Date.now
    }
    public var wrappedTags: String {
        tags ?? ""
    }

    var friendsArray: [CachedFriend] {
        let set = friends as? Set<CachedFriend> ?? []
        return set.sorted {
            $0.wrappedFriendName < $1.wrappedFriendName
        }
    }

}

extension CachedUser : Identifiable {

}

Thanks in advance for any advice.

2      

Hi! Not sure if this is the reason but I think you forgot to add cachedFriend to cachedUser...

 for friend in user.friends {
                let cachedFriend = CachedFriend(context: moc)
                cachedFriend.id = friend.id
                cachedFriend.name = friend.name
                cachedUser.addToFriends(cachedFriend) // this line is missing
            }

2      

Though actually in your extension to CachedUser you have to change

This line

 @NSManaged public var friends: String?

to this

@NSManaged public var friends: NSSet?

your friends is not a string but a set.

and in any case you have to add your friends as indicated in the post above otherwise friends prorperty will be empty : )

2      

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!

Thanks for this, I did have it written down to add as per Pauls instructions when I got stuck, but I couldn't work out where it points as there is no ".addToFriends" mentioned anywhere else in the cachedUser? Apologies if I'm missing something obvious, not feeling well at the minute and the brain fog is something else ha ha.

2      

It took me sometime to realize how it works ))) too many things happen behind the scenes.

When you generate code manually via Xcode menu bar, choose Editor > Create NSManagedObject Subclass. XCode adds this code, if i am not mistaken.

// MARK: Generated accessors for friends
extension CachedUser {

    @objc(addFriendsObject:)
    @NSManaged public func addToFriends(_ value: CachedFriend)

    @objc(removeFriendsObject:)
    @NSManaged public func removeFromFriends(_ value: CachedFriend)

    @objc(addFriends:)
    @NSManaged public func addToFriends(_ values: NSSet)

    @objc(removeFriends:)
    @NSManaged public func removeFromFriends(_ values: NSSet)

}

you can read more here https://developer.apple.com/documentation/coredata/modeling_data/generating_code

But even if you have it generated automatically by xcode, without creating those files manually, you still have access to those methods. As it still generated behind the scenes :) So if you did not delete those lines added by XCode you will be able to access them.

3      

Thank you so much. I thought I was going crazy as I was getting the error that no member "addToFriends" exists...I did the Xcode menu bar, choose Editor > Create NSManagedObject Subclass at the beginning and didn't delete any code in that file so I have no clue why it wasn't there?!

The extension was there but completely empty. How strange. Thank you so much for your help! Big sigh of relief (until tomorrows code that is ;) )

2      

You know, I suspect that when manually generating code you had this line set up as String not Set

@NSManaged public var friends: NSSet?

Again, as far as I can recall this part of code marked as

// MARK: Generated accessors for friends

relates to Relations in your core data model. i.e. if there are no relationship setup, this part won't be generated... and you had it as string...

PS

Check in your data model that you have CachedFriend entity -> Relationship: user, in Inspector Pane -> Type: To One

And CachedUser entity -> Relationship: friends, in Inspector Pane -> Type: To Many

3      

Yes I think I did have friends set up as a string to begin with, generated the code, then remembered to set the relationship...doh This makes total sense now. Worked out I was having an allergic reaction whilst doing this challenge earlier, a miracle I wrote any code lol

2      

OMG. Hope you're doing well now 😳.

3      

oh yeah, doing much better now thanks. I have gastro & allergy conditions that make me have allergic reactions fairly often. Grew up with it so very used to it, even if it still annoys the hell out of me when i'm trying to concentrate on code ha.

2      

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!

Reply to this topic…

You need to create an account or log in to reply.

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.