Post

Replies

Boosts

Views

Activity

SwiftData background inserts using ModelActor do not trigger SwiftUI @Query view updates, but background deletes do
In the WWDC 23 lounge, this exchange with @Dave N (Apple) indicated that using ModelActor is the right way to do background work using swift concurrency. https://developer.apple.com/forums/thread/731338 I've figured out how to use this approach to do background work using swift concurrency (inside a Task), and example code is below for those who find it useful, however I'm not seeing view updates when the background work is complete. There seems to be no way to trigger a view update based on a @Query when inserts happen in a ModelActor’s context. However, if a delete happens on the ModelActor context, this DOES trigger a view redraw. I believe this is a bug because I expect the behavior to be the same for inserts and deletes. I've submitted this as Feedback FB12689036. Below is a minimal project which is the default SwiftData template, where the Add and Delete buttons point to a ModelActor instead of the main view ModelContext. You will see that using the addItem function in the ModelActor does not trigger a UI update in ContentView. However if you relaunch the app the added Item will be present. In contrast, using the delete function on the ModelActor context does trigger an immediate view update in ContentView. If this is intended behavior, we need a way to merge changes from background contexts, similar to what is described in the Core Data document “Loading and Displaying a Large Data Feed”: https://developer.apple.com/documentation/swiftui/loading_and_displaying_a_large_data_feed In Core Data we have automaticallyMergesChangesFromParent and mergeChanges(fromContextDidSave:) to do this manually. There seems to be no equivalent for Swift Data. If anyone has solved this problem of merging changes from other contexts, or can confirm that this is a bug, please let me know. import SwiftUI import SwiftData struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var items: [Item] @State private var simpleModelActor: SimpleModelActor! var body: some View { NavigationView { List { ForEach(items) { item in NavigationLink { Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") } label: { Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) } } .onDelete(perform: deleteItems) } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { EditButton() } ToolbarItem { Button(action: addItem) { Label("Add Item", systemImage: "plus") } } } Text("Select an item") } .onAppear { simpleModelActor = SimpleModelActor(modelContainer: modelContext.container) } } private func addItem() { Task { await simpleModelActor.addItem() } } private func deleteItems(offsets: IndexSet) { Task { for index in offsets { await simpleModelActor.delete(itemWithID: items[index].objectID) } } } } import Foundation import SwiftData final actor SimpleModelActor: ModelActor { let executor: any ModelExecutor init(modelContainer: ModelContainer) { let modelContext = ModelContext(modelContainer) executor = DefaultModelExecutor(context: modelContext) } func addItem() { let newItem = Item(timestamp: Date()) context.insert(newItem) try! context.save() // this does not impact a re-display by the @Query in ContentView. I would have expected it to cause a view redraw. } func delete(itemWithID itemID: Item.ID) { let item = context.object(with: itemID) context.delete(object: item) // this DOES cause a view redraw in ContentView. It triggers an update by @Query. // try! context.save() // this makes do difference to view redraw behavior. } }
6
10
3.9k
Jul ’23
ImageRenderer fails to render contents of ScrollViews
Is everyone else seeing that ImageRenderer is unable to render the contents of a ScrollView? When using the ImageRenderer class introduced in iOS16, the render will not contain any Views that are inside of a ScrollView. The ScrollView contents within the rendered image will be blank. This is surprising to me since I would think ScrollView use is pretty common, and so ImageRenderer would have been tested with it. Is there any documentation out there explaining what types of Views or scenarios will cause a failure of ImageRenderer? To reproduce, run the ContentView below inside an iOS SwiftUI project. Tap the “Render this View to Image” button. Expected results: the green-bordered Image should show a screenshot of the entire View, including the contents of the ScrollView (“Text inside a ScrollView”). Actual results: the rendered screenshot shows the region of the ScrollView, but there are no contents shown. It is blank. import SwiftUI struct ContentView: View {     @State private var renderedImage: UIImage?     var body: some View {         VStack {             Button("Render this View to Image") {                 renderedImage = ImageRenderer(content: self).uiImage!             }             VStack {                 Text("rendered image")                 Image(uiImage: renderedImage ?? UIImage(systemName: "xmark")!)                     .resizable()                     .aspectRatio(contentMode: .fit)             }             .frame(height: 200, alignment: .center)             .padding(5).border(.green, width: 5)             VStack {                 Text("A ScrollView is shown below")                 ScrollView {                     VStack {                         Text("Text inside a ScrollView")                         Text("Text inside a ScrollView")                         Text("Text inside a ScrollView")                         Text("Text inside a ScrollView")                     }                     .fixedSize()                 }             }             .padding(5).border(.red, width: 5)             .frame(height: 100)         }         .padding()     } }
4
1
2.7k
Jul ’23