Random SwiftUI crashes

Hi!

I write a time table like app where entries on a day planner are created and modified. It works well but since the latest additions I get random crashes after clicking an UI control.

Any clue how to debug such an issue?

The crash log looks like this:

Code Type:             ARM-64 (Native)
Parent Process:        launchd [1]
OS Version:            macOS 13.3 (22E252)
Report Version:        12

Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BREAKPOINT (SIGTRAP)
Exception Codes:       0x0000000000000001, 0x000000019472f6f4

Termination Reason:    Namespace SIGNAL, Code 5 Trace/BPT trap: 5
Terminating Process:   exc handler [2623]

Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
0   libswiftCore.dylib            	       0x19472f6f4 _assertionFailure(_:_:flags:) + 188
1   libswiftCore.dylib            	       0x19488f8d4 KEY_TYPE_OF_DICTIONARY_VIOLATES_HASHABLE_REQUIREMENTS(_:) + 208
2   libswiftCore.dylib            	       0x1947d8868 _NativeDictionary._unsafeInsertNew(key:value:) + 316
3   libswiftCore.dylib            	       0x194890b08 _NativeDictionary.extractDictionary(using:count:) + 740
4   libswiftCore.dylib            	       0x194892364 closure #1 in _NativeDictionary.filter(_:) + 780
5   libswiftCore.dylib            	       0x1947bcdf0 _NativeDictionary.filter(_:) + 260
6   libswiftCore.dylib            	       0x1947bc8b0 Dictionary.filter(_:) + 3004
7   SwiftUI                       	       0x1ae872e30 0x1adcb1000 + 12328496
8   SwiftUI                       	       0x1adea10c8 0x1adcb1000 + 2031816
9   SwiftUI                       	       0x1adeae364 0x1adcb1000 + 2085732
10  SwiftUI                       	       0x1ae053c08 0x1adcb1000 + 3812360
11  SwiftUI                       	       0x1ae052550 0x1adcb1000 + 3806544
12  SwiftUI                       	       0x1ae050c34 0x1adcb1000 + 3800116
13  SwiftUI                       	       0x1ae15e980 0x1adcb1000 + 4905344
14  AttributeGraph                	       0x1af552cec AG::Graph::UpdateStack::update() + 520
15  AttributeGraph                	       0x1af553494 AG::Graph::update_attribute(AG::data::ptr<AG::Node>, unsigned int) + 424
16  AttributeGraph                	       0x1af5615d8 AG::Subgraph::update(unsigned int) + 864
17  SwiftUI                       	       0x1af131174 0x1adcb1000 + 21496180
18  SwiftUI                       	       0x1af1325fc 0x1adcb1000 + 21501436
19  SwiftUI                       	       0x1ae315204 0x1adcb1000 + 6701572
20  SwiftUI                       	       0x1aee53c68 0x1adcb1000 + 18492520
21  SwiftUI                       	       0x1aee5278c 0x1adcb1000 + 18487180
22  SwiftUI                       	       0x1ae3151c4 0x1adcb1000 + 6701508
23  SwiftUI                       	       0x1af1325c4 0x1adcb1000 + 21501380
24  SwiftUI                       	       0x1af1324b0 0x1adcb1000 + 21501104
25  SwiftUI                       	       0x1ae828ce8 0x1adcb1000 + 12025064
26  SwiftUI                       	       0x1aef15914 0x1adcb1000 + 19286292
27  SwiftUI                       	       0x1aef1885c 0x1adcb1000 + 19298396
28  SwiftUI                       	       0x1aef188b4 0x1adcb1000 + 19298484
29  CoreFoundation                	       0x185b09ac4 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 36
30  CoreFoundation                	       0x185b099b0 __CFRunLoopDoObservers + 532
31  CoreFoundation                	       0x185b085e8 CFRunLoopRunSpecific + 704
32  HIToolbox                     	       0x18f33ddf4 RunCurrentEventLoopInMode + 292
33  HIToolbox                     	       0x18f33da84 ReceiveNextEventCommon + 220
34  HIToolbox                     	       0x18f33d988 _BlockUntilNextEventMatchingListInModeWithFilter + 76
35  AppKit                        	       0x188d27f58 _DPSNextEvent + 636
36  AppKit                        	       0x188d270f4 -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 716
37  AppKit                        	       0x188d1b558 -[NSApplication run] + 464
38  AppKit                        	       0x188cf29a8 NSApplicationMain + 880
39  SwiftUI                       	       0x1add4d974 0x1adcb1000 + 641396
40  SwiftUI                       	       0x1aeeacfa0 0x1adcb1000 + 18857888
41  SwiftUI                       	       0x1ae72e43c 0x1adcb1000 + 10998844
42  ITR-Stundenplan               	       0x100eabec8 static ITR_StundenplanApp.$main() + 40 (ITR_StundenplanApp.swift:18)
43  ITR-Stundenplan               	       0x100eac4c8 main + 12
44  dyld                          	       0x1856d3f28 start + 2236

Please show the code of the control that causes crash. And also show relevant code that let understand what the control actions do.

I need to create a simplified example which takes some time…

Now I was able to build a (not so small) example which shows the issue.

It shows two rectangles which can be selected. After selection it is possible to move them via the toolbar buttons.

Click a rectangle and move it up and down. Change selection and repeat. After some tries it will crash. I post the example in the next post because the message size limit is reached.

import SwiftUI

struct vTime : Codable {
    var hour : Int
    var min  : Int
}

struct RecDate : Codable {
    var day  : Int
    var start: vTime
    var end  : vTime
    
    private enum CodingKeys : String, CodingKey {
        case day
        case start
        case end
    }
    
    func duration() -> CGFloat {
        let dTime : CGFloat = abs(CGFloat(end.hour - start.hour) * 4.0 + CGFloat(end.min - start.min) / 15.0)
        return dTime
    }
}

class Record : Hashable, Equatable, Identifiable, ObservableObject {
    let vID = UUID()
    @Published var subject : String
    @Published var date   : RecDate
    
    init(subject: String, date : RecDate) {
        self.subject = subject
        self.date = date
    }
    
    static func == (lhs: Record, rhs: Record) -> Bool {
        var r = lhs.subject.compare(rhs.subject) == .orderedSame
        r = r && lhs.date.start.hour == rhs.date.start.hour
        r = r && lhs.date.start.min == rhs.date.start.min
        r = r && lhs.date.end.hour == rhs.date.end.hour
        r = r && lhs.date.end.min == rhs.date.end.min
        r = r && lhs.vID == rhs.vID
        return r
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(subject)
        hasher.combine(date.start.hour)
        hasher.combine(date.start.min)
        hasher.combine(date.end.hour)
        hasher.combine(date.end.min)
        hasher.combine(vID)
    }
    
    func isSelected(appSelection : UUID?) -> Bool {
        return appSelection == vID
    }
    
    public func incDate() {
        if !(self.date.end.hour == 19 && self.date.end.min == 0) {
            self.date.start.min += 15
            self.date.end.min += 15
            if self.date.start.min == 60 {
                self.date.start.min = 0
                self.date.start.hour += 1
            }
            if self.date.end.min == 60 {
                self.date.end.min = 0
                self.date.end.hour += 1
            }
        }
    }
    
    public func decDate() {
        if !(self.date.start.hour == 8 && self.date.start.min == 0) {
            self.date.start.min -= 15
            self.date.end.min -= 15
            if self.date.start.min < 0 {
                self.date.start.min = 45
                self.date.start.hour -= 1
            }
            if self.date.end.min < 0 {
                self.date.end.min = 45
                self.date.end.hour -= 1
            }
        }
    }
    
}

public class RecordHandler : ObservableObject {
    @Published var records : [Record] = [
        Record(subject: "banana", date: RecDate(day: 0, start: vTime(hour: 8, min: 0), end: vTime(hour: 10, min: 0))),
        Record(subject: "strawberry", date: RecDate(day: 1, start: vTime(hour: 11, min: 0), end: vTime(hour: 14, min: 0)))
    ]
    @Published var selectedID  : UUID? = nil

    func updateRecord(oldID : UUID?, newV : Record) {
        self.records.removeAll { value in
            value.vID == oldID
        }
        self.records.append(newV)
    }
    
    func setSel(newID : UUID) {
        if selectedID == nil {
            selectedID = newID
        } else {
            if selectedID == newID {
                selectedID = nil
            } else {
                selectedID = newID
            }
        }
    }
}

struct EntryView: View {
    @EnvironmentObject var appData : RecordHandler
    @ObservedObject var setup : Record
    @State private var showDataView : Bool = false
    
    struct SelView : View {
        var body: some View {
            ZStack {
                Rectangle()
                    .inset(by: -2)
                    .stroke(.blue, lineWidth: 5.0)
            }
        }
    }

    var body: some View {
        let qCount = setup.date.duration()
        let height = qCount * 10.0
        let xPos   = 100.0
        let yPos   = 7.0 + (CGFloat(setup.date.start.hour) - 8.0) * 40.0 + CGFloat(setup.date.start.min) / 1.5 + (qCount - 1.0) * 5.0
        ZStack {
            RoundedRectangle(cornerRadius: 10)
                .foregroundColor(.red.opacity(0.80))
            VStack {
                Text(setup.subject)
                    .bold()
                    .allowsTightening(true)
                    .multilineTextAlignment(.center)
                    .truncationMode(.tail)
            }
            .font(.system(size: 10))
            .padding([.leading, .trailing], 5)
            if setup.isSelected(appSelection: appData.selectedID) {
                SelView()
            }
        }
        .frame(width: 200, height: height)
        .position(x: xPos, y: yPos)
        .onTapGesture(count: 1) {
            appData.setSel(newID: setup.vID)
        }
    }
}

struct RoomView: View {
    @EnvironmentObject var appData : RecordHandler

    var body: some View {
        VStack() {
            VStack {
                ForEach(appData.records, id: \.self) { value in
                    EntryView(setup: value)
                        .environmentObject(appData)
                }
            }
        }
    }
}

struct DayView: View {
    @EnvironmentObject var appData : RecordHandler
    var day : Int
    
    var body: some View {
        VStack {
            ForEach(appData.records, id: \.self) { value in
                if value.date.day == day {
                    EntryView(setup: value)
                        .environmentObject(appData)
                }
            }
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var appData : RecordHandler

    var body: some View {
        NavigationStack {
            HStack {
                DayView(day: 0)
                    .environmentObject(appData)
                DayView(day: 1)
                    .environmentObject(appData)
            }
        }
        .toolbar {
            ToolbarItemGroup(placement: .automatic) {
                Button("↑") {
                    if let ds = appData.records.first(where: { $0.vID == appData.selectedID!}) {
                        ds.decDate()
                    }
                }
                .disabled(appData.selectedID == nil)
                Button("↓") {
                    if let ds = appData.records.first(where: { $0.vID == appData.selectedID!}) {
                        ds.incDate()
                    }
                }
                .disabled(appData.selectedID == nil)
            }
        }
    }
}

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

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

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

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

Here is a smaller version which does also crash (now tested with new Xcode 14.3):

import SwiftUI

struct vTime : Codable {
    var hour : Int
    var min  : Int
}

struct RecDate : Codable {
    var day  : Int
    var start: vTime
    var end  : vTime
    
    private enum CodingKeys : String, CodingKey {
        case day
        case start
        case end
    }
    
    func duration() -> CGFloat {
        let dTime : CGFloat = abs(CGFloat(end.hour - start.hour) * 4.0 + CGFloat(end.min - start.min) / 15.0)
        return dTime
    }
}

class Record : Hashable, Equatable, Identifiable, ObservableObject {
    let vID = UUID()
    @Published var subject : String
    @Published var date   : RecDate
    
    init(subject: String, date : RecDate) {
        self.subject = subject
        self.date = date
    }
    
    static func == (lhs: Record, rhs: Record) -> Bool {
        var r = lhs.subject.compare(rhs.subject) == .orderedSame
        r = r && lhs.date.start.hour == rhs.date.start.hour
        r = r && lhs.date.start.min == rhs.date.start.min
        r = r && lhs.date.end.hour == rhs.date.end.hour
        r = r && lhs.date.end.min == rhs.date.end.min
        r = r && lhs.vID == rhs.vID
        return r
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(subject)
        hasher.combine(date.start.hour)
        hasher.combine(date.start.min)
        hasher.combine(date.end.hour)
        hasher.combine(date.end.min)
        hasher.combine(vID)
    }
    
    func isSelected(appSelection : UUID?) -> Bool {
        return appSelection == vID
    }
    
    public func incDate() {
        if !(self.date.end.hour == 19 && self.date.end.min == 0) {
            self.date.start.min += 15
            self.date.end.min += 15
            if self.date.start.min == 60 {
                self.date.start.min = 0
                self.date.start.hour += 1
            }
            if self.date.end.min == 60 {
                self.date.end.min = 0
                self.date.end.hour += 1
            }
        }
    }
    
    public func decDate() {
        if !(self.date.start.hour == 8 && self.date.start.min == 0) {
            self.date.start.min -= 15
            self.date.end.min -= 15
            if self.date.start.min < 0 {
                self.date.start.min = 45
                self.date.start.hour -= 1
            }
            if self.date.end.min < 0 {
                self.date.end.min = 45
                self.date.end.hour -= 1
            }
        }
    }
    
}

public class RecordHandler : ObservableObject {
    @Published var records : [Record] = [
        Record(subject: "banana", date: RecDate(day: 0, start: vTime(hour: 8, min: 0), end: vTime(hour: 10, min: 0))),
        Record(subject: "strawberry", date: RecDate(day: 1, start: vTime(hour: 11, min: 0), end: vTime(hour: 14, min: 0)))
    ]
    @Published var selectedID  : UUID? = nil

    func setSel(newID : UUID) {
        if selectedID == nil {
            selectedID = newID
        } else {
            if selectedID == newID {
                selectedID = nil
            } else {
                selectedID = newID
            }
        }
    }
}

struct EntryView: View {
    @EnvironmentObject var appData : RecordHandler
    @ObservedObject var setup : Record
    @State private var showDataView : Bool = false
    
    struct SelView : View {
        var body: some View {
            Rectangle()
                .inset(by: -2)
                .stroke(.blue, lineWidth: 5.0)
        }
    }

    var body: some View {
        let qCount = setup.date.duration()
        let height = qCount * 10.0
        let xPos   = 100.0
        let yPos   = 7.0 + (CGFloat(setup.date.start.hour) - 8.0) * 40.0 + CGFloat(setup.date.start.min) / 1.5 + (qCount - 1.0) * 5.0
        ZStack {
            RoundedRectangle(cornerRadius: 10)
                .foregroundColor(.red.opacity(0.80))
            Text(setup.subject)
            if setup.isSelected(appSelection: appData.selectedID) {
                SelView()
            }
        }
        .frame(width: 200, height: height)
        .position(x: xPos, y: yPos)
        .onTapGesture(count: 1) {
            appData.setSel(newID: setup.vID)
        }
    }
}

struct DayView: View {
    @EnvironmentObject var appData : RecordHandler
    var day : Int
    
    var body: some View {
        VStack {
            ForEach(appData.records, id: \.self) { value in
                if value.date.day == day {
                    EntryView(setup: value)
                        .environmentObject(appData)
                }
            }
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var appData : RecordHandler

    var body: some View {
        NavigationStack {
            HStack {
                DayView(day: 0)
                    .environmentObject(appData)
                DayView(day: 1)
                    .environmentObject(appData)
            }
        }
        .toolbar {
            ToolbarItemGroup(placement: .automatic) {
                Button("↑") {
                    if let ds = appData.records.first(where: { $0.vID == appData.selectedID!}) {
                        ds.decDate()
                    }
                }
                .disabled(appData.selectedID == nil)
                Button("↓") {
                    if let ds = appData.records.first(where: { $0.vID == appData.selectedID!}) {
                        ds.incDate()
                    }
                }
                .disabled(appData.selectedID == nil)
            }
        }
    }
}

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

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

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

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(appData)
        }
    }
}
Random SwiftUI crashes
 
 
Q