Search code examples
swiftswiftuiswiftdata

ForEach complaining about duplicate values with SwiftData


I have a Category model that's defined like so:

@Model
final class Category {
    @Attribute(.unique) var id: UUID
    var name: String
    var parent_id: UUID //categories can be children of other categories

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

And I'm getting my categories from an API call and putting it into my View:

import SwiftUI
import SwiftData

struct CategoryView: View {
    @Environment(\.modelContext) private var modelContext
    
    @Query private var categories: [Category]
    @Query(filter: #Predicate<Category>{ $0.parent_id == nil })
        private var top_level_categories: [Category]
    
    var spacing: CGFloat = 25

    var body: some View {
        HStack() {
            Text("Categories")
                .font(.title.bold())
            
            Spacer()
            Text("see all")
        }
        .padding([.bottom, .top], 0)
        VStack(spacing: 20) {
            ScrollView(.horizontal) {
                HStack(spacing: spacing) {
                    ForEach(top_level_categories) { category in
                        Text(category.name!)

                    }
                }
            }
        }
        .onAppear{
            getCategories()
        }
    }
    
    func getCategories()  {
        get_refresh_token { token in
            guard let token = token else {
                return
            }
            var urlRequest = URLRequest (url: URL(string:"https://api.test.com/categories")!)
            urlRequest.httpMethod = "GET"
            urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
            urlRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
            
            URLSession.shared
                .dataTask(with: urlRequest) { (data, response, error) in
                    do {
                        if let data = data {
                            let c = try JSONDecoder().decode([Category].self, from: data)
                            c.forEach { modelContext.insert($0) }
                            try? modelContext.save()
                        }
                    }
                    catch {
                        print(error)
                    }
                }.resume()
            
        }
    }
    
    
}

This runs fine the first time I run the app but when I run it again, I get the following error:

ForEach<Array<Category>, UUID, Text>: the ID XXXXXX-XXXX-XXXX-XXXX-XXXXXX occurs multiple times within the collection, this will give undefined results!

Not sure why this is happening since I thought putting the @Attribute(.unique) on ID means that the same category won't get added twice? Also unsure if I'm representing the parent/child relationship correctly. All I really need to be able to do is filter to 'top level categories' (i.e. no parent_id), and get the sub-categories for a given category.

Thanks!


Solution

  • Based on this thread, wrapping the modelContext.insert and modelContext.save in DispatchQueue.main.async fixed the issue.