admin管理员组

文章数量:1134587

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)
            }
        }
    }
}

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)
            }
        }
    }
}
Share Improve this question asked Jan 7 at 22:13 The-WolfThe-Wolf 436 bronze badges 5
  • Is it possible that you already have a Production with the same name? – Paulw11 Commented Jan 8 at 5:12
  • 1 There is no constraint on the name property in Production so it should be possible to have duplicates. – Joakim Danielson Commented Jan 8 at 8:16
  • I can't reproduce using you code. Is this some simplified version or should it be reproducible with only the posted code? Also, what OS do you use? – Joakim Danielson Commented Jan 8 at 8:19
  • @JoakimDanielson This is the only code needed, I just loaded up an empty project, pasted in the code, and got it again. I'm using Xcode 16.2 with iPhone SE Second Generation. – The-Wolf Commented Jan 8 at 9:16
  • 1 Ok I can reproduce on iOS (but not macOS). – Joakim Danielson Commented Jan 8 at 10:15
Add a comment  | 

1 Answer 1

Reset to default 1

I am not sure about the reason for the crash since it's not known (to my knowledge) how the hash value is calculated for PersistentModel objects but I believe the crash is related to how and when the hash value gets re-calculated for either the production property and/or the elements in the characters property that can lead to a crash because something gets updated in the wrong order.

It can be quite tricky to handle a view where two different types of models gets created and then handle a relationship between them without running into issues. So the below no-thrills solution, that has not generated any crashes when I tested it, simplified the whole solution by not working with any kind of model properties and instead uses properties for the model attributes and creates and inserts all new models in a controlled manner when the "Save" button is pressed. In this way everything is very local (in-memory) to the view while editing.

struct AddProductionView: View {
    @Environment(\.dismiss) private var dismiss
    @Environment(\.modelContext) var modelContext
    
    @State private var productionName = ""
    @State private var characterNames: [String] = []
    
    @State private var characterName: String = ""
    @State private var selectedCharacter: String?
        
    var body: some View {
        NavigationStack {
            Form {
                Section("Details") {
                    TextField("Title", text: $productionName)
                }
                Section("Characters") {
                    List(characterNames, id: \.self) { character in
                        Text(character)
                    }
                    HStack {
                        TextField("Character", text: $characterName)
                        Button("Add") {
                            characterNames.append(characterName)
                            characterName = ""
                        }
                        .disabled(characterName.isEmpty || characterNames.contains(characterName))
                    }
                    if !characterNames.isEmpty {
                        Picker("Select your role", selection: $selectedCharacter) {
                            Text("Select")
                                .tag(nil as String?)
                            ForEach(characterNames, id: \.self) { character in
                                Text(character)
                                    .tag(character as String?)
                            }
                        }
                        .pickerStyle(.menu)
                    }
                }
            }
            .toolbar {
                Button("Save") {
                    let production = Production(name: productionName)
                    modelContext.insert(production)
                    
                    for name in characterNames {
                        let character = Character(name: name, production: production)
                        if let selectedCharacter, name == selectedCharacter {
                            character.myCharacter = true
                        }
                    }
                    do {
                        try modelContext.save()
                    } catch {
                        print("Failed to save context: \(error)")
                    }
                    dismiss()
                }
                .disabled(productionName.isEmpty || selectedCharacter == nil)
            }
        }
    }
}

本文标签: swiftFatal error Duplicate keys of type 39AnyHashable39 were found in a DictionaryStack Overflow