Post

Replies

Boosts

Views

Activity

Swift Data Merge Contexts Publish
I need to edit a persisted SwiftData item in an isolated context, because if not is isolated with every change triggers expensive computations. When the edit is done and I save the temporal context. I expect that the temporal context is merged with the main context and all the views that have a dependency with the edited Item.id be invalidated and updated. But not. When I dismiss the ItemEditView and temporalContext.save() is triggered the ItemNormalView that has a dependency with the Item.id remains not updated so I have to do a non elegant either reliable workaround. What is the canonical way of do it? The most relevant code: struct ItemEditView: View { @Bindable var temporalItem: Item let temporalContext: ModelContext @Binding var updater: Bool init(itemID: PersistentIdentifier, container: ModelContainer, updater: Binding<Bool>){ temporalContext = ModelContext(container) temporalContext.autosaveEnabled = false self.temporalItem = temporalContext.model(for: itemID) as! Item self._updater = updater } var body: some View { TextField("", text: $temporalItem.name) .onDisappear{ Task{ try! temporalContext.save() //Workaround: try? await Task.sleep(nanoseconds:1_000_000_000) updater.toggle() } } } } struct ItemView: View { let item: Item @Environment(\.modelContext) private var modelContext @State private var itemEditorViewModalViewIsPresented = false @State private var updater = false var body: some View { VStack{ ItemNornalView(item: item, updater: $updater) Button("Show Edit Modal View"){ itemEditorViewModalViewIsPresented.toggle() } } .sheet(isPresented: $itemEditorViewModalViewIsPresented){ ItemEditView(itemID: item.id, container: modelContext.container, updater: $updater) } } } struct ItemNornalView: View { let item: Item @Binding var updater: Bool var body: some View { let _ = Self._printChanges() Text("\(updater)").scaleEffect(0).hidden() Text("Item name: \(item.name)") } } The rest of the code: struct ContentView: View { var body: some View { TabView{ MetricsView().tabItem { Text("Metrics") } ItemsList().tabItem { Text("List") } } } } struct MetricsView: View { @Query private var items: [Item] var body: some View { Chart{ BarMark(x: .value("x", "Average"), y: .value("y", expensiveComputation(items: items))) } } private func expensiveComputation(items: [Item])->Double{ //... return 1.0 } } struct ItemsList: View { @Query private var items: [Item] @Environment(\.modelContext) private var modelContext @State var path = NavigationPath() var body: some View { let _ = Self._printChanges() NavigationStack(path: $path) { List { ForEach(items) { item in Button{ path.append(item) }label:{ ItemRowView(item: item) } } .onDelete(perform: deleteItems) } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { EditButton() } ToolbarItem { Button(action: addItem) { Label("Add Item", systemImage: "plus") } } } .navigationDestination(for: Item.self){ item in ItemView(item:item) } } } private func addItem() { withAnimation { let newItem = Item() modelContext.insert(newItem) try! modelContext.save() } } private func deleteItems(offsets: IndexSet) { withAnimation { for index in offsets { modelContext.delete(items[index]) } } } }
0
0
305
Mar ’24
Swift Data Merge Contexts Publish and SwiftUI Dependency
Expected behaviour: When I press the button I expect the label change from "Original" to "Edited" Obtained behaviour: This only happens in scenario A) and B) not C), the desired. Scenario A: action: update2(item:) and both labels Scenario B: action: update(itemID:container:) and label: Text(item.name) Scenario C: action: update(itemID:container:) and label: ItemRow(item:) import SwiftUI import SwiftData struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var items: [Item] var body: some View { NavigationSplitView { List { ForEach(items) { item in Button(action: { update(itemID: item.id, container: modelContext.container) // update2(item: item) }, label: { ItemRow(item: item) // Text(item.name) }) } .onDelete(perform: deleteItems) } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { EditButton() } ToolbarItem { Button(action: addItem) { Label("Add Item", systemImage: "plus") } } } } detail: { Text("Select an item") } } private func addItem() { withAnimation { let newItem = Item(name: "Original") modelContext.insert(newItem) //modelContext.save() } } private func deleteItems(offsets: IndexSet) { withAnimation { for index in offsets { modelContext.delete(items[index]) } } } } struct ItemRow: View { let item: Item var body: some View { Text(item.name) } } @Model final class Item { var name: String init(name: String) { self.name = name } } func update(itemID: PersistentIdentifier, container: ModelContainer){ let editModelContext = ModelContext(container) editModelContext.autosaveEnabled = false let editModel = editModelContext.model(for: itemID) as! Item editModel.name = "Edited" try! editModelContext.save() } func update2(item: Item){ item.name = "Edited" }
0
1
384
Dec ’23
Swift Charts Animation setting X and Y domain doesn't works
I want the line grow without a canvas resize so I need to scale the canvas before the animation. If I apply both X and Y Scale modifiers to set the domain, animation doesn't work, if I comment either of both, yes... Any idea why and any workaround? import SwiftUI import Charts struct Entry: Identifiable { var id = UUID() var time: Double var value: Double } struct ContentView: View { @State var data: [Entry] = [ .init(time: 0, value: 0), .init(time: 1, value: 1)] var body: some View { VStack{ Button("+"){ data.append(.init(time: 2, value: 3)) } Chart(data){entry in LineMark(x: .value("time", entry.time), y: .value("value", entry.value)) } .chartXScale(domain: 0...3) .chartYScale(domain: 0...3) .padding() .animation(.easeIn(duration:3), value: data) } } }
0
0
727
Sep ’23
SwiftData inverse Relationship stop working in XCode 15 beta 7
This approach works before XCode 15 beta 7: @Model final class Item { var name: String @Relationship(inverse:\Note.item) var notes: [Note] init(name: String = "Item name") { self.name = name self.notes = [] } } @Model final class Note { var name: String var item: Item init(name: String = "Note name", item: Item) { self.name = name self.item = item } } @main struct LifeKPIs_SwiftData_PlaygroundApp: App { var body: some Scene { WindowGroup { ContentView() } .modelContainer(for: [Item.self, Note.self]) } } struct ContentView: View { @Environment(\.modelContext) private var modelContext var body: some View { //... let item = Item() modelContext.insert(item) //... let note = Note( item: item) //Error here } } Thread 1: "Illegal attempt to establish a relationship 'item' between objects in different contexts (source = <NSManagedObject: 0x6000021c3020> (entity: Note; id: 0x60000030ef00 x-coredata:///Note/t0C99AC11-423B-4942-9B5D-C9F9E6A4370E7; data: {\n item = nil;\n name = "Note name";\n}) , destination = <NSManagedObject: 0x60000212d400> (entity: Item; id: 0xabdd96b5daee1528 x-coredata://4C4C1274-3F87-425E-837F-F2AEFD751084/Item/p1; data: {\n name = "New Item 0";\n notes = (\n );\n}))"
2
0
1.6k
Aug ’23
SwiftData. Predicate. Issue accessing self (Entity) inside a Predicate
@Model final class Entry { var value: Int var date: Date var item: Item? init(value: Int, date: Date) { self.value = value self.date = date } } @Model final class Item { var name: String @Relationship(deleteRule:.cascade, inverse: \Entry.item) var entries: [Entry] init(name: String) { self.name = name entries = [] } } extension Item { func checkingDate(in context: ModelContext)->Date{ try! context.fetch(FetchDescriptor<Entry>(predicate:#Predicate<Entry>{$0.item == self})).last!.date } } same if self == self Why I'm fetching and not directly accessing entries: https://developer.apple.com/forums/thread/735735
3
0
1.1k
Aug ’23
SwiftData: Accessing collection from computed property forces dependency update ?!? and this compute property and so on... cycle
If I comment let _ = entries everything goes well, but if not, accessing(not editing) the collection I don't know why forces the view update (dependency change in items) and with the view update the computed property checkingDate is triggered firing the view update... and so on. Cycle Why accessing entries forces view update ? If I change let _ = entries for let _ = name everything goes well so I conclude that is a problem with collection... Models: @Model final class Item { var name: String @Relationship(deleteRule:.cascade) var entries: [Entry] init(name: String) { self.name = name entries = [] } } extension Item { @Transient var checkingDate: Date { let _ = entries // < -- !!! //... return Date() } } @Model final class Entry { var value: Int var date: Date init(value: Int, date: Date) { self.value = value self.date = date } } View: struct ContentView: View { @Query(sort: \Item.name) private var items: [Item] @Environment(\.modelContext) private var context var body: some View { let _ = Self._printChanges() List{ ForEach(items){item in Text("Name:\(item.name), checking date: \(item.checkingDate, format: Date.FormatStyle(date: .numeric, time: .standard))") } } } }
4
0
652
Aug ’23
Unexpected Atomic behaviour
With a threadsPerGrid of (10,1,1) and: [[kernel]] void compute_shader (device atomic_int&amp; incremental [[buffer(0)]],                 ushort lid [[thread_position_in_threadgroup]] ){       threadgroup atomic_int local_atomic {0};       atomic_fetch_add_explicit(&amp;local_atomic, 1, memory_order_relaxed);       threadgroup_barrier(mem_flags::mem_threadgroup);       if(lid == 0) {     int local_non_atomic = atomic_load_explicit(&amp;local_atomic, memory_order_relaxed);     atomic_fetch_add_explicit(&amp;incremental, local_non_atomic, memory_order_relaxed);   } } I expect: 10 20 30 ... But I get: 1125974026 11259740362000908258 832823256 ... github.com/quaternionboy/Atomic
1
0
565
Apr ’21
cast metal::atomic_int to int
Is there any way to cast metal::atomic_intto int in MSLibrary like C++ Standard Library ? C++:  std::atomicint atom {10};  int num = (int)atom; MSL: kernel void compute_shader (device metal::atomic_int&amp; incremental [[buffer(0)]],threadgroup atomic_int&amp; local [[threadgroup(0)]],ushort lid [[thread_position_in_threadgroup]] ){   atomic_fetch_add_explicit(&amp;local, 1, memory_order_relaxed);   threadgroup_barrier(mem_flags::mem_threadgroup);   if(lid == 0) {     atomic_fetch_add_explicit(&amp;incremental, (int)local, memory_order_relaxed);//ERROR     atomic_fetch_add_explicit(&amp;incremental, as_typeint(local), memory_order_relaxed);//ERROR   } }
1
0
1.2k
Mar ’21