-
-
Save henningko/586663db19aff5c80cd386a30b0af3e5 to your computer and use it in GitHub Desktop.
SwiftData Context Crash
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import SwiftData | |
import EventKit | |
@Model | |
class Event: Hashable, Equatable { | |
var id: String | |
var name: String | |
var eventNotes: String? | |
@Relationship var notes: [Note]? | |
// @Transient does not publish (iOS bug?), use .ephemeral instead | |
@Attribute(.ephemeral) var isSelected: Bool = false | |
init(_ name: String = "Unnamed Event", calendarId: String, eventNotes: String) { | |
self.id = calendarId | |
self.name = name | |
self.eventNotes = eventNotes | |
} | |
init(from calendarEvent: EKEvent) { | |
self.id = calendarEvent.eventIdentifier | |
self.name = calendarEvent.title | |
self.eventNotes = calendarEvent.notes ?? "" | |
} | |
func hash(into hasher: inout Hasher) { | |
hasher.combine(id) | |
} | |
static func == (lhs: Event, rhs: Event) -> Bool { | |
return lhs.id == rhs.id | |
} | |
static func loadEvents(date: Date = Date()) -> [Event] { | |
let eventStore: EKEventStore = EKEventStore() | |
var events: [Event] = [] | |
requestEventAccess(eventStore: eventStore) { granted in | |
if granted { | |
let endDate = date.addingTimeInterval(15 * 60) // 15 minutes from now | |
let startDate = date.addingTimeInterval(-24 * 60 * 60) // 24 hours ago | |
let predicate = eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: nil) | |
let calendarEvents = eventStore.events(matching: predicate) | |
// Filter the events to get ones that are either ongoing or have ended in the last 15 minutes. | |
let filteredEvents = calendarEvents.filter { calendarEvent in | |
if let eventEnd = calendarEvent.endDate { | |
let fifteenMinutesAgo = date.addingTimeInterval(-15 * 60) | |
return eventEnd > fifteenMinutesAgo | |
} | |
return false | |
} | |
events = filteredEvents.map { calendarEvent in | |
return Event(from: calendarEvent) | |
} | |
} | |
} | |
return events | |
} | |
static func requestEventAccess(eventStore: EKEventStore, completion: @escaping (Bool) -> Void) { | |
switch EKEventStore.authorizationStatus(for: .event) { | |
case .authorized: | |
completion(true) | |
case .denied, .restricted: | |
completion(false) | |
case .fullAccess: | |
completion(true) | |
case .writeOnly: | |
completion(false) | |
case .notDetermined: | |
eventStore.requestFullAccessToEvents { granted, error in | |
completion(granted) | |
} | |
@unknown default: | |
completion(false) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import SwiftData | |
@Model | |
class Note { | |
var id: UUID | |
var created: Date | |
var content: String | |
var location: Location? | |
var websites: [Website]? | |
@Relationship(inverse: \Event.notes) | |
var events: [Event]? | |
init(_ content: String, created: Date = .now, events: [Event] = []) { | |
self.id = UUID() | |
self.created = created | |
self.content = content | |
self.events = events | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import SwiftData | |
struct NoteInputView: View { | |
@Environment(\.modelContext) private var context | |
var scrollProxy: ScrollViewProxy | |
@State private var newNoteContent = "" | |
@State private var showSelectLocationSheet: Bool = false | |
@State private var events: [Event] = [] | |
var body: some View { | |
VStack { | |
HStack(spacing: 16) { | |
SelectEventButton(events: $events) | |
Button(action: { | |
self.showSelectLocationSheet = true | |
}, label: { | |
Label("Location", systemImage: "location") | |
.font(.caption) | |
}) | |
.popover(isPresented: $showSelectLocationSheet) { | |
SelectLocationSheet() | |
} | |
} | |
.padding(.bottom) | |
HStack() { | |
TextField("Enter a quick note...", text: $newNoteContent, axis: .vertical) | |
.lineLimit(1...5) | |
.padding(.horizontal) | |
.padding(.vertical, 4) | |
.background( | |
RoundedRectangle(cornerRadius: 12) | |
.stroke(Color.gray.opacity(0.1), lineWidth: 1) | |
.background( | |
RoundedRectangle(cornerRadius: 12) | |
.fill(Color.background) | |
) | |
) | |
Button(action: { | |
let newNote: Note = addNote() | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { | |
withAnimation { | |
scrollProxy.scrollTo(newNote) | |
} | |
} | |
newNoteContent = "" | |
}) { | |
Image(systemName: "arrow.up.circle.fill") | |
.font(.title) | |
} | |
} | |
} | |
.padding([.horizontal, .vertical]) | |
.overlay( | |
Rectangle() | |
.fill(Color.gray.opacity(0.1)) | |
.frame(height: 1), alignment: .top | |
) | |
.background(Color.secondaryBackground) | |
} | |
func addNote() -> Note { | |
let selectedEvents = events.filter({ $0.isSelected }) | |
// Option A: This crashes | |
let note = Note(newNoteContent, events: selectedEvents) | |
// Option B: This works | |
selectedEvents.forEach({context.insert($0)}) | |
let note = Note(newNoteContent, events: events) | |
// Option C: This also works, despite notes also always referring to the same events | |
let note = Note(newNoteContent, events: Event.loadEvents()) | |
context.insert(note) // <-- crash occurs here | |
do { | |
try context.save() | |
} catch { | |
print(error) // it is actually context.insert that crashes | |
} | |
return note | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import EventKit | |
struct SelectEventSheet: View { | |
@Binding var events: [Event] | |
@Environment(\.dismiss) private var dismiss | |
var body: some View { | |
NavigationView { | |
List(events) { event in | |
Button { | |
event.isSelected = !event.isSelected | |
} label: { | |
if event.isSelected { | |
Label(event.name, systemImage: "checkmark") | |
} else { | |
Label(event.name, systemImage: "") | |
} | |
} | |
.foregroundColor(.primary) | |
} | |
.navigationTitle("Select Events") | |
.toolbarTitleDisplayMode(.inline) | |
.toolbar { | |
ToolbarItem(placement: .confirmationAction) { | |
Button { | |
dismiss() | |
} label: { | |
Text("Done") | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Complete error: