Post

Replies

Boosts

Views

Activity

navigationTitle Scroll Behavior Fails when NavigationSplitView has Overlay or ZStack
I have tried updating my app form NavigationView to NavigationSplitView. Previously, my code looked like this: // ContentView.swift import SwiftUI import SwiftData struct ContentView: View { @Environment(\.modelContext) private var context @Query(sort: \Note.created, order: .reverse) var notes: [Note] var body: some View { NavigationView { ScrollViewReader { scrollProxy in ZStack() { NoteListView(notes: notes) NoteInputView(scrollProxy: scrollProxy) .frame(maxHeight: .infinity, alignment: .bottom) } } .navigationBarTitle(Text("Notes")) } } } The reason I am using a ZStack is because the NoteInputView has a keyboard that should be dismissible on tap. However, there is a bug that prevents swipeActions from receiving a tap if onTapGesture is used elsewhere, see description here: https://stackoverflow.com/questions/77162726/swiftui-swipeactions-not-working-when-tap-gesture-is-used-elsewhere-focusstate When using NavigationView, this behaves just fine: Scrolled down: Scrolled to top: Using NavigationSplitView, my code looks like this: // ContentView.swift import SwiftUI import SwiftData struct ContentView: View { @Environment(\.modelContext) private var context @Query(sort: \Note.created, order: .reverse) var notes: [Note] @State var selection: Set<Note> = [] var body: some View { ScrollViewReader { scrollProxy in NavigationSplitView { ZStack { NoteListView(notes: notes, selection: $selection) NoteInputView(scrollProxy: scrollProxy) .frame(maxHeight: .infinity, alignment: .bottom) } .navigationTitle(Text("Notes")) } detail: { if let noteSelected = selection.first { NoteView(note: noteSelected) } else { Text("No note selected") .foregroundStyle(Color.secondary) } } } } } Where now the toolbar/title doesn't scroll appropriately: Removing ZStack and putting the NoteInputView into an .overlay results in the same behavior. When I remove the ZStack and my NoteInput, the scrolling also works as desired. Wrapping the ZStack into a ScrollView hides the notes. Seems to me that this might be a bug, or does anyone have another workaround in mind?
1
0
551
Sep ’23
SwiftData: Inserting two entities with same relationship target crashes
I have a Model Class Note: @Model class Note { var id: UUID var created: Date var content: String @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 } } And Event: @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 ?? "" } ... static func loadEvents(date: Date = Date()) -> [Event] { ... } } I have the following View hierarchy NoteInputView which has @State var events: [Event] = [] SelectEventButton which has @Binding var events: [Event] and calls Event.loadEvent() to retrieve list of events SelectEventSheet which has @Binding var events: [Event] and lets the user toggle isSelected GitHub Gist with all relevant files Adding notes with same events crashes... With this setup, I attempt so save new notes in NoteInputView by calling addNote: func addNote() -> Note { let selectedEvents = events.filter({ $0.isSelected }) let note = Note(newNoteContent, events: selectedEvents) context.insert(note) do { try context.save() } catch { print(error) } return note } This works for the first note after opening the app, or if every subsequent note has a different event selected. However, storing a second note with the same event crashes with the following error: "Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'events' between objects in different contexts" (complete error see here) The error occurs at context.insert, which doesn't throw. If I force quit the app, and then add a note with the same events as an already persisted note, no error is thrown (until a I add another note with the same event without force-quitting). ... but not because one cannot refer to the same events twice It's not a problem of referring to the same events, as the following code also works fine for multiple notes: func addNote() -> Note { // This works, despite notes also always referring to the same events let note = Note(newNoteContent, events: Event.loadEvents()) context.insert(note) do { try context.save() } catch { print(error) } return note } . ... workaround? Manually adding events to the context before adding it to the notes One workaround seems to be to add the events to the context before adding the note: func addNote() -> Note { let selectedEvents = events.filter({ $0.isSelected }) selectedEvents.forEach({context.insert($0)}) let note = Note(newNoteContent, events: events) context.insert(note) do { try context.save() } catch { print(error) } return note } . ... but why? While this works, I cannot quite make sense of this. It seems that passing events around between views may be the culprit, or that loadEvents is called in a child view. Would love some advice, since this doesn't seem like intended behavior.
0
0
811
Sep ’23