GO FURTHER, FASTER: Try the Swift Career Accelerator today! >>

SOLVED: Day 61 - Implementing SwiftData, ModelContext.insert not working

Forums > 100 Days of SwiftUI

Having trouble implementing SwiftData on my working solution to day 60, Milestone: Projects 10-12.

Specifically, I'm seeing an error on ContentView.swift, "Instance method 'insert' requires that '[User]' conform to 'PersistentModel'"

Does anyone have any insight on what the issue could be? It seems I've wired up SwiftData correctly, and have all of the needed imports, but I'm having trouble debugging the issue.

Also, i've tried to search the web and these forums for example solutions, but it seems that this is the new version of this challenge. From looking at past implementations, day 61 used to be a CoreData problem.

AllMyFriends_ConsolidationVApp.swift

@main
struct AllMyFriends_ConsolidationVApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: User.self)
    }
}

Friend.swift

class Friend: Codable {
    var id: String
    var name: String

    init(id: String, name: String) {
        self.id = id
        self.name = name
    }
}

User.swift

@Model class User: Codable, Hashable {
//struct User: Codable {
    enum CodingKeys: String, CodingKey {
        case id
        case isActive
        case name
        case age
        case company
        case email
        case address
        case about
        case registered
        case tags
        case friends
    }

    var id: String
    var isActive: Bool
    var name: String
    var age: Int
    var company: String
    var email: String
    var address: String
    var about: String
    var registered: Date
    var tags: [String]
    var friends: [Friend]

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(self.id, forKey: .id)
        try container.encode(self.isActive, forKey: .isActive)
        try container.encode(self.name, forKey: .name)
        try container.encode(self.age, forKey: .age)
        try container.encode(self.company, forKey: .company)
        try container.encode(self.email, forKey: .email)
        try container.encode(self.address, forKey: .address)
        try container.encode(self.about, forKey: .about)
        try container.encode(self.registered, forKey: .registered)
        try container.encode(self.tags, forKey: .tags)
        try container.encode(self.friends, forKey: .friends)

    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        //don't need to write CodingKeys because all of the data names match
        self.id = try container.decode(String.self, forKey: .id)
        self.isActive = try container.decode(Bool.self, forKey: .isActive)
        self.name = try container.decode(String.self, forKey: .name)
        self.age = try container.decode(Int.self, forKey: .age)
        self.company = try container.decode(String.self, forKey: .company)
        self.email = try container.decode(String.self, forKey: .email)
        self.address = try container.decode(String.self, forKey: .address)
        self.about = try container.decode(String.self, forKey: .about)
        self.registered = try container.decode(Date.self, forKey: .registered)
        self.tags = try container.decode([String].self, forKey: .tags)
        self.friends = try container.decode([Friend].self, forKey: .friends)
    }

    init(id: String, isActive: Bool, name: String, age: Int, company: String, email: String, address: String, about: String, registered: Date, tags: [String], friends: [Friend]) {
        self.id = id
        self.isActive = isActive
        self.name = name
        self.age = age
        self.company = company
        self.email = email
        self.address = address
        self.about = about
        self.registered = registered
        self.tags = tags
        self.friends = friends
    }

}

ContentView.swift

struct ContentView: View {
    @Environment(\.modelContext) var modelContext
    @Query(sort: \User.name) var users: [User]

    var body: some View {
        NavigationStack {
            List(users, id: \.id) { user in
                NavigationLink(value: user) {
                    Text(user.name)
                }
            }
            .navigationTitle("All My Friends")
            .task {
                if users.isEmpty {
                await loadFriendData()
                print(users.count)
                }
            }
            .navigationDestination(for: User.self) { user in
                DetailView(user: user)
            }
        }
    }

    func loadFriendData() async {
        guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {
            print("invalid url")
            return
        }

        do {
            let (responseData, _) = try await URLSession.shared.data(from: url)

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

            if let decodedResponse = try decoder.decode([User]?.self, from: responseData) {
                modelContext.insert(decodedResponse)
            } else {
                print("if let block not working")
            }

        } catch {
            print(error.localizedDescription)
            print(String(describing: error))
        }
    }
}

DetailView.swift

struct DetailView: View {
    var user: User
    var body: some View {
        List {
            UserAttribute(label: "Address", attribute: user.address)
            UserAttribute(label: "Company", attribute: user.company)
            UserAttribute(label: "Email", attribute: user.email)
            UserAttribute(label: "Joined", attribute: returnDateString(user.registered))
//            Spacer()
            ScrollView(.horizontal) {
                Text("Friends")
                    .font(.caption)
                    .foregroundColor(.accentColor)
                    .padding(EdgeInsets(top: 0, leading: -.infinity, bottom: 0, trailing: 0))
                HStack {
                    ForEach(user.friends, id: \.self.id) { friend in
                        Text(friend.name)
                            .padding()
                    }
                }
            }
            .padding()
//            Spacer()
            UserAttribute(label: "About", attribute: user.about)
        }

        .navigationTitle(user.name)
        .toolbar {
            ToolbarItem {
                user.isActive ?
                    Text("🟢 Active")
                    .foregroundStyle(Color.green)
                :
                    Text("🔴 Inactive")
                    .foregroundStyle(Color.red)
            }
        }
    }

    func returnDateString(_ date: Date) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .short
        return dateFormatter.string(from: date)
    }
}

struct UserAttribute: View {
    var label: String
    var attribute: String

    var body: some View {
        VStack(alignment: .leading) {
            Text(label)
                .font(.caption)
                .padding(EdgeInsets(top: 0, leading: 0, bottom: 7, trailing: 0))
                .foregroundColor(.accentColor)
            Text(attribute)
        }
    }
}

2      

Also, I've tried to isolate what the issue is and see if I could hard code an object into the model and edited my ContentView.swift to the below.

I'm getting weird results, including the Preview on Xcode constantly crashing and the NavigationLink routing being funky (needing to press back several times before getting back to homepage).

So maybe something else is going wrong???

struct ContentView: View {
    @Environment(\.modelContext) var modelContext
    @Query(sort: \User.name) var users: [User]

    var body: some View {
        NavigationStack {
            List(users, id: \.id) { user in
                NavigationLink(value: user) {
                    Text(user.name)
                }
            }
            .navigationTitle("All My Friends")
            .task {
                if users.isEmpty {
                //   await loadFriendData()
                await loadDataModelContext()
                print(users.count)
                }
            }
            .navigationDestination(for: User.self) { user in
                DetailView(user: user)
            }
        }
    }
    func loadDataModelContext() async {

        do {
            let dummyUser = User(id: "1290423", isActive: false, name: "Jay Z", age: 47, company: "Roc Nation", email: "jay@rocnation.com", address: "111 New York", about: "rapper", registered: Date(), tags: ["random", "placeholder", "tags"],
                friends: [
                    Friend(id: "1221092", name: "J. Cole"),
                    Friend(id: "129130312", name: "Rihanna")
                ]
            )
            modelContext.insert(dummyUser)
        } catch {
            print(error.localizedDescription)
            print(String(describing: error))
        }
    }

}

2      

From the SwiftData tutorial, you can see that Paul inserts one instance of the object at a time with the .insert method rather than inserting a whole array of Users at once.

I encountered the same issue as you but solved it like this:

What I did was to loop through the fetched results and the insert each model object. Just amend your loadData() method at this part.

if let decodedUsers = try decoder.decode([User]?.self, from: responseData) {               
           for user in decodedUsers {
                modelContext.insert(user)
             }               

5      

Thank you, @swiftasroy ! That turned out to be the issue. I assumed that you could just pass the entire array of objects inside to the model.

Turns out that you can only insert into Swift Data one object at a time. This doesn't seem super optimal and after doing some googling I found an article from Paul where he shows how you can batch insert objects into a Swift Data model in the background. Seems practical for larger data sets.

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 and A/B test your entire paywall UI 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.