Search code examples
swiftswiftuiswiftdata

Fatal error: Duplicate keys of type 'AnyHashable' were found in a Dictionary


I get the following fatal error when the user clicks Save in AddProductionView.

Fatal error: Duplicate keys of type 'AnyHashable' were found in a Dictionary. This usually means either that the type violates Hashable's requirements, or that members of such a dictionary were mutated after insertion.

As far as I’m aware, SwiftData automatically makes its models conform to Hashable, so this shouldn’t be a problem.

I think it has something to do with the picker, but for the life of me I can’t see what.

This error occurs about 75% of the time when Save is clicked.

Any help would be greatly appreciated…

Here is my code:

import SwiftUI
import SwiftData

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .modelContainer(for: Character.self, isAutosaveEnabled: false)
        }
    }
}

@Model
final class Character {
    var name: String
    var production: Production
    var myCharacter: Bool
    
    init(name: String, production: Production, myCharacter: Bool = false) {
        self.name = name
        self.production = production
        self.myCharacter = myCharacter
    }
}

@Model
final class Production {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

struct ContentView: View {
    @State private var showingSheet = false
    var body: some View {
        Button("Add", systemImage: "plus") {
            showingSheet.toggle()
        }
        .sheet(isPresented: $showingSheet) {
            AddProductionView()
        }
    }
}

struct AddProductionView: View {
    @Environment(\.dismiss) private var dismiss
    @Environment(\.modelContext) var modelContext
    
    @State var production = Production(name: "")
    @Query var characters: [Character]
    
    @State private var characterName: String = ""
    @State private var selectedCharacter: Character?
    
    var filteredCharacters: [Character] {
        characters.filter { $0.production == production }
    }
    
    var body: some View {
        NavigationStack {
            Form {
                Section("Details") {
                    TextField("Title", text: $production.name)
                }
                Section("Characters") {
                    List(filteredCharacters) { character in
                        Text(character.name)
                    }
                    HStack {
                        TextField("Character", text: $characterName)
                        Button("Add") {
                            let newCharacter = Character(name: characterName, production: production)
                            modelContext.insert(newCharacter)
                            characterName = ""
                        }
                        .disabled(characterName.isEmpty)
                    }
                    if !filteredCharacters.isEmpty {
                        Picker("Select your role", selection: $selectedCharacter) {
                            Text("Select")
                                .tag(nil as Character?)
                            ForEach(filteredCharacters) { character in
                                Text(character.name)
                                    .tag(character as Character?)
                            }
                        }
                        .pickerStyle(.menu)
                    }
                }
            }
            .toolbar {
                Button("Save") { //Fatal error: Duplicate keys of type 'AnyHashable' were found in a Dictionary. This usually means either that the type violates Hashable's requirements, or that members of such a dictionary were mutated after insertion.
                    if let selectedCharacter = selectedCharacter {
                        selectedCharacter.myCharacter = true
                    }
                    modelContext.insert(production)
                    do {
                        try modelContext.save()
                    } catch {
                        print("Failed to save context: \(error)")
                    }
                    dismiss()
                }
                .disabled(production.name.isEmpty || selectedCharacter == nil)
            }
        }
    }
}

Solution

  • The solution (thanks to Chris Parker) was to add var id = UUID() to the model definition so the final result would look something like:

    @Model
    final class Character {
        var id = UUID()
        var name: String
        var production: Production?
        var myCharacter: Bool
        
        init(name: String, production: Production?, myCharacter: Bool = false) {
            self.name = name
            self.production = production
            self.myCharacter = myCharacter
        }
    }
    
    @Model
    final class Production {
        var id = UUID()
        var name: String
    
        @Relationship(deleteRule: .cascade, inverse: \Act.production) var acts: [Act] = []
        
        init(name: String) {
            self.name = name
        }
    }