Post

Replies

Boosts

Views

Activity

Reply to WeatherKit always fails with 404 for specific lat/long/dates with forecastDaily
I want to update this with feedback that I received from Apple Developer Technical Support. "There is no workaround DTS can provide for the bug reports mentioned (FB13608710,FB13551253,FB13188858); they are all still under investigation." So if you are seeing intermittent failures to download historical data using WeatherKit, or permanent failures at specific locations and dates, Apple is aware of it and it's not something you are doing wrong on your end. This includes failures where a request does not throw an error, but it silently fails to retrieve data from the middle of the range. I have implemented a stand-alone SwiftUI view that demonstrates these failures to request historical data at this gist. You of course need to have a Xcode project with a WeatherKit Capability. Hopefully this can be resolved by Apple, but we will be pursuing an alternate weather data vendor, as historical data availability is a tent pole of our app. Note that we have not seen problems with WeatherKit providing recent or forecast data, but it seems that historical data is not as much of a focus for WeatherKit as Time Machine was for Dark Sky. I can only imagine that few apps are relying on their historical data.
Topic: App & System Services SubTopic: General Tags:
Feb ’24
Reply to WeatherKit always fails with 404 for specific lat/long/dates with forecastDaily
I’ll also mention that this is particularly frustrating because we also routinely see requests that initially fail with 404, but if we retry a few times (sometimes even a day later) it will eventually succeed. So we have built our app to retry a few times if we get a 404 error. However, in the cases posted above, they never succeed. It is not a good experience that we seem to get the same error from WeatherKit for queries that temporarily fail and those that permanently fail, because we will have to try to distinguish the two cases based on heuristics around how often it failed for particular dates / locations for each user. WeatherKit should really throw different errors, or return an empty array, if data is never going to be returned vs a temporary failure. Of course it should never throw an error for a valid query, but given that this is happening we would like to see better errors.
Topic: App & System Services SubTopic: General Tags:
Feb ’24
Reply to SwiftData + CloudKit process for deduplication / consuming relevant store changes?
I'd like to respond to DelawareMathGuys's suggestion to use fatbobman's SwiftDataKit to implement history processing, but it is too big to be in reply to a root comment so I'm putting it here. Fatbobman has written some great blog posts about SwiftData, and it deserves a real response. I have actually already implemented fabobman's approach in a dev branch of my project. I don't think it's viable for production in a commercial app for a few reasons: In a Core Data history processor you can execute a query with a predicate, but in FBM's hack, you cannot, because SwiftData itself does not support passing predicates to the history fetch. Normally you set the transaction author to be something like "app" or "widget" and if the author is missing you can assume it is coming from CloudKit. So you query your history for author != excluded authors, and you can process relevant changes from the network. It is impossible to do any pre-filtering on the transaction so you have to process every transaction that has happened and filter in memory. let fetchRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: timestamp) // In SwiftData, the fetchRequest.fetchRequest created by fetchHistory is nil and predicate cannot be set. You can't set merge policies (like NSMergeByPropertyObjectTrumpMergePolicy) in SwiftData using FBM's approach, so you can't easily control how the merge happens. His approach is fully based on his SwiftDataKit extensions, which is based entirely on undocumented internal implementation details of SwiftData. For example, to get a SwiftData PersistentIdentifier, he makes a mock Codable struct that can be populated with undocumented elements of a Core Data NSManagedObject to build a struct that can be encoded to JSON, that can be decoded back to a SwiftData PersistentIdentifier. So it depends on the undocumented structure of the PersistentIdentifier and its relationship to the underlying Core Data object. That's probably stable... // from https://github.com/fatbobman/SwiftDataKit/blob/main/Sources/SwiftDataKit/CoreData/NSManagedObjectID.swift // Compute PersistentIdentifier from NSManagedObjectID public extension NSManagedObjectID { // Compute PersistentIdentifier from NSManagedObjectID var persistentIdentifier: PersistentIdentifier? { guard let storeIdentifier, let entityName else { return nil } let json = PersistentIdentifierJSON( implementation: .init(primaryKey: primaryKey, uriRepresentation: uriRepresentation(), isTemporary: isTemporaryID, storeIdentifier: storeIdentifier, entityName: entityName) ) let encoder = JSONEncoder() guard let data = try? encoder.encode(json) else { return nil } let decoder = JSONDecoder() return try? decoder.decode(PersistentIdentifier.self, from: data) } } // Extensions to expose needed implementation details extension NSManagedObjectID { // Primary key is last path component of URI var primaryKey: String { uriRepresentation().lastPathComponent } // Store identifier is host of URI var storeIdentifier: String? { guard let identifier = uriRepresentation().host() else { return nil } return identifier } // Entity name from entity name var entityName: String? { guard let entityName = entity.name else { return nil } return entityName } } So, as I worked on trying his approach, I felt that it was a clever hack that I wouldn't be comfortable depend on in production, to ultimately implement a solution that isn't very good (request all transactions from all sources and filter in memory without being able to set a merge policy for the final set of transactions). I think what he made is a neat workaround, but for a commercial app I think it would be better to implement the parallel Core Data stack and do real history change processing. Or fix the gaps discussed above with unavailable predicates and merge policies. But best of all would be a mechanism to do this in SwiftData itself.
Feb ’24
Reply to SwiftData does not work on a background Task even inside a custom ModelActor.
It turns out that it's not just the context that runs on the main thread, but the actor appears to be isolated to the main thread as well. If we create a normal actor, it runs on thread from the thread pool (not the main thread). However, if we create a ModelActor, it appears to inherit the thread from its parent. You can test this with Thread.isMainThread. @ModelActor final actor MyActor { var contextIsMainThread: Bool? var actorIsMainThread: Bool? func determineIfContextIsMainThread() { try? modelContext.transaction { self.contextIsMainThread = Thread.isMainThread } } func determineIfActorIsMainThread() { self.actorIsMainThread = Thread.isMainThread } } As has been discussed above, you get this behavior based on how your model actor is initiated. content .onAppear { actorCreatedOnAppear = MyActor(modelContainer: mainContext.container) // actor and modelContext are on main thread Task { self.actorCreatedOnAppearTask = MyActor(modelContainer: mainContext.container) // actor and modelContext are on main thread } Task.detached { self.actorCreatedOnAppearDetachedTask = MyActor(modelContainer: mainContext.container) // actor and modelContext are NOT on main thread. This is the only option which matches the behavior of other Swift actors. } } I've submitted FB13450413 with a project demonstrating this.
Dec ’23
Reply to Do I need to add my own unique id?
I suspect that the persistent identifier is a wrapper for the underlying Core Data objectID: NSManagedObjectID. The caveat with using the core data NSManagedObjectID is that it is unique, and permanent, EXCEPT when the object is first created and before it is persisted. There is an initial temporary objectID assigned that is later replaced with the permanent id. If you want to use the SwiftData PersistentIdentifier, I would recommend checking that this behavior does or does not transfer over from Core Data. Personally, I've been burned enough by the complexity of dealing with that Core Data NSManagedObjectID that I just create an id: UUID on my objects rather than rely on the persistence layer id.
Aug ’23
Reply to Xcode 15 beta 5 issue when using @Query data to update Button label
I'm seeing a related issue in my app as well. I also have a query that I overwrite a view init similar to your _favorites = Query(filter: filter, sort: \FavoriteModel.createdDate) However, very strangely, I have an interaction with my TabView navigation. I have a navigator class @Observable public final class Navigator { public var route: Route? = nil } and my tabview binds to it TabView(selection: $navigator.route) { Text("view 1") .tabItem { Label("Recent", systemImage: "chart.bar.xaxis") } .tag(Route.locationDetail as Route?) } If I instead get rid of the Navigator, and just bind to a local @State var route: Route in the view, then it eliminates the problem in one app. In another app we've got IP, we just have to comment out the = Query(...) change in the view initializer (thereby breaking the view)
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jul ’23
Reply to SwiftData background inserts using ModelActor do not trigger SwiftUI @Query view updates, but background deletes do
In Xcode 15 beta 5 this behavior is perhaps fixed? My sample project does see view refreshes when adding an item via a ModelActor. However, after creating and deleting a few Items, the List will suddenly only show a single Item. If I relaunch the app, then all Items will appear again. It seems like maybe we are getting progress on fixing the ModelActor view refreshes (great!) but there are still some bugs. BTW my sample code above needs a few small tweaks to run on Xcode 15 beta 5. ContentView 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) } } } } #Preview { ContentView() .modelContainer(for: Item.self, inMemory: true) } SimpleModelActor 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(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. } } Item import Foundation import SwiftData @Model final class Item: Identifiable { var timestamp: Date init(timestamp: Date) { self.timestamp = timestamp } } And of course the main App file needs .modelContainer(for: Item.self) added to the window group.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jul ’23
Reply to WeatherKit error on iOS 17 - macOS 14
I'm seeing the same issue. I created a minimal WeatherKit app and it runs fine on Xcode 15 beta 4 using a iOS 16.4 simulator. On an iOS 17 beta 3 simulator it fails with the following error message: Aborting silent interpolation: missing cached hourlyForecast; location=CLLocationCoordinate2D(latitude: 35.157019886059835, longitude: -85.34119394760656) Failed to generate jwt token for: com.apple.weatherkit.authservice with error: Error Domain=WeatherDaemon.WDSJWTAuthenticatorServiceListener.Errors Code=2 "(null)" Encountered an error when fetching weather data subset; location=<+35.15701989,-85.34119395> +/- 0.00m (speed -1.00 mps / course -1.00) @ 7/13/23, 8:29:48 PM Eastern Daylight Time, error=WeatherDaemon.WDSJWTAuthenticatorServiceListener.Errors 2 Error Domain=WeatherDaemon.WDSJWTAuthenticatorServiceListener.Errors Code=2 "(null)" I've filed this as feedback FB12602396
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jul ’23
Reply to ImageRenderer fails to render contents of ScrollViews
I wanted to close this out. I got a response to my Feedback indicating that this is the expected behavior. ImageRenderer output only includes views that SwiftUI renders, such as text, images, shapes, and composite views of these types. It does not render views provided by native platform frameworks (AppKit and UIKit) such as web views, media players, and some controls. For these views, ImageRenderer displays a placeholder image, similar to the behavior of drawingGroup(opaque:colorMode:). I wish there was a way for me to know which views are provided by native frameworks so I could predict if ImageRenderer would fail, but there you go.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jul ’23