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

Hi!

I have an issue where data modification leads to a Hash error which seems to be wrong. I posted already in Apple Forum but there is no solution, yet.
I made a simplified example which shows the error after a few clicks on the shown buttons. Any idea how to solve this issue?

import SwiftUI
enum Weekdays : String, CaseIterable {
    case Montag
    case Dienstag
    case Mittwoch
    case Donnerstag
    case Freitag
    case Samstag
    //case Sonntag
}

class Vorlesung : Hashable, Equatable, Identifiable, ObservableObject {
    @Published var subject : String
    @Published var day     : Weekdays
    let vID = UUID()
    
    init(subject: String, day : Weekdays) {
        self.subject = subject
        self.day     = day
    }
    
    static func == (lhs: Vorlesung, rhs: Vorlesung) -> Bool {
        let result = lhs.subject.compare(rhs.subject) == .orderedSame
        return result
    }
    func hash(into hasher: inout Hasher) {
        hasher.combine(vID) // UUID
    }
}

public class VorlesungsHandler : ObservableObject {
    let dayList : [Weekdays] = [.Montag, .Dienstag]//, .Mittwoch, .Donnerstag, .Freitag]
    @Published var vorlesungen : [Vorlesung] = [
        Vorlesung(subject: "banana", day: .Montag),
        Vorlesung(subject: "strawberry", day: .Montag),
        Vorlesung(subject: "apple", day: .Montag),
        Vorlesung(subject: "pear", day: .Montag)
    ]
    func updateVorlesung(oldID : UUID?, newV : Vorlesung) {
        self.vorlesungen.removeAll { value in
            value.vID == oldID
        }
        self.vorlesungen.append(newV)
    }
}

struct PlanEntryView: View {
    @EnvironmentObject var appData : VorlesungsHandler
    @ObservedObject var setup : Vorlesung
    @State private var showDataView : Bool = false
    
    var body: some View {
        ZStack {
            Button(setup.subject) {
                let switchedDay : Weekdays = setup.day == .Montag ? .Dienstag : .Montag
                appData.updateVorlesung(oldID: setup.vID, newV: Vorlesung(subject: setup.subject, day: switchedDay))
            }
        }
    }
}


struct RoomView: View {
    @EnvironmentObject var appData : VorlesungsHandler
    var day  : Weekdays

    var body: some View {
        VStack() {
            VStack {
                ForEach(appData.vorlesungen, id: \.self) { value in
                    if (value.day == day) {
                        PlanEntryView(setup: value)
                            .environmentObject(appData)
                    }
                }
            }
        }
    }
}

struct DayView: View {
    @EnvironmentObject var appData : VorlesungsHandler

    var day : Weekdays
    var body: some View {
        VStack {
            Text(day.rawValue)
            HStack {
                RoomView(day: day)
                    .environmentObject(appData)
            }
            Spacer()
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var appData : VorlesungsHandler

    var body: some View {
        VStack {
            ForEach(appData.dayList, id: \.self) { value in
                DayView(day: value)
                    .environmentObject(appData)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    @StateObject static var previewData = VorlesungsHandler()

    static var previews: some View {
        ContentView()
            .environmentObject(previewData)
    }
}

@main
struct crasherApp: App {
    @StateObject private var appData = VorlesungsHandler()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(appData)
        }
    }
}

From Hashable documentation:

Hashing a value means feeding its essential components into a hash function, represented by the Hasher type. Essential components are those that contribute to the type’s implementation of Equatable. Two instances that are equal must feed the same values to Hasher in hash(into:), in the same order.

your code:

    static func == (lhs: Vorlesung, rhs: Vorlesung) -> Bool {
        let result = lhs.subject.compare(rhs.subject) == .orderedSame
        return result
    }
    func hash(into hasher: inout Hasher) {
        hasher.combine(vID) // UUID
    }

Currently your hash and == are using completely different properties, which leads to conflict between them. Are two Vorlesung with the same subject but different vID equal to each other, or not? According to == they are equal, but according to hash they are not. We need to fix that

You need to decide what makes two Vorlesung the same, and use these properties in both == and hash. Usually in a struct it's all of the properties, and that's handled by the autogenerated implementation

3 Likes

Many thanks for your reply.
I did change the code so that equals and the hasher now use the same fields and there is no error anymore.

static func == (lhs: Vorlesung, rhs: Vorlesung) -> Bool {
    let result = lhs.subject.compare(rhs.subject) == .orderedSame
    return result
}
func hash(into hasher: inout Hasher) {
    hasher.combine(subject)
}