Hi! I use Tips with CloudKit and it works very well, however when a user want to remove their data from CloudKit, how to do that?
In CoreData with CloudKit area, NSPersistentCloudKitContainer have purgeObjectsAndRecordsInZone to delete both local managed objects and CloudKit records, however there is no information about the TipKit deletion.
Does anyone know ideas?
Delve into the world of built-in app and system services available to developers. Discuss leveraging these services to enhance your app's functionality and user experience.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I have not had any successful Schema Migration with CloudKit so far so I'm trying to do with with just very basic attributes, with multiple Versioned Schemas
This is the code in my App Main
var sharedModelContainer: ModelContainer = {
let schema = Schema(versionedSchema: AppSchemaV4.self)
do {
return try ModelContainer(
for: schema,
migrationPlan: AppMigrationPlan.self,
configurations: ModelConfiguration(cloudKitDatabase: .automatic))
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ItemListView()
}
.modelContainer(sharedModelContainer)
}
And this is the code for my MigrationPlan and VersionedSchemas.
typealias Item = AppSchemaV4.Item3
enum AppMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[AppSchemaV1.self, AppSchemaV2.self, AppSchemaV3.self, AppSchemaV4.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2, migrateV2toV3, migrateV3toV4]
}
static let migrateV1toV2 = MigrationStage.lightweight(
fromVersion: AppSchemaV1.self,
toVersion: AppSchemaV2.self
)
static let migrateV2toV3 = MigrationStage.lightweight(
fromVersion: AppSchemaV2.self,
toVersion: AppSchemaV3.self
)
static let migrateV3toV4 = MigrationStage.custom(
fromVersion: AppSchemaV3.self,
toVersion: AppSchemaV4.self,
willMigrate: nil,
didMigrate: { context in
// Fetch all Item1 instances
let item1Descriptor = FetchDescriptor<AppSchemaV3.Item1>()
let items1 = try context.fetch(item1Descriptor)
// Fetch all Item2 instances
let item2Descriptor = FetchDescriptor<AppSchemaV3.Item2>()
let items2 = try context.fetch(item2Descriptor)
// Convert Item1 to Item3
for item in items1 {
let newItem = AppSchemaV4.Item3(name: item.name, text: "Migrated from Item1 on \(item.date)")
context.insert(newItem)
}
// Convert Item2 to Item3
for item in items2 {
let newItem = AppSchemaV4.Item3(name: item.name, text: "Migrated from Item2 with value \(item.value)")
context.insert(newItem)
}
try? context.save()
}
)
}
enum AppSchemaV1: VersionedSchema {
static var versionIdentifier: Schema.Version = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] {
[Item1.self]
}
@Model class Item1 {
var name: String = ""
init(name: String) {
self.name = name
}
}
}
enum AppSchemaV2: VersionedSchema {
static var versionIdentifier: Schema.Version = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type] {
[Item1.self]
}
@Model class Item1 {
var name: String = ""
var date: Date = Date()
init(name: String) {
self.name = name
self.date = Date()
}
}
}
enum AppSchemaV3: VersionedSchema {
static var versionIdentifier: Schema.Version = Schema.Version(3, 0, 0)
static var models: [any PersistentModel.Type] {
[Item1.self, Item2.self]
}
@Model class Item1 {
var name: String = ""
var date: Date = Date()
init(name: String) {
self.name = name
self.date = Date()
}
}
@Model class Item2 {
var name: String = ""
var value: Int = 0
init(name: String, value: Int) {
self.name = name
self.value = value
}
}
}
enum AppSchemaV4: VersionedSchema {
static var versionIdentifier: Schema.Version = Schema.Version(4, 0, 0)
static var models: [any PersistentModel.Type] {
[Item1.self, Item2.self, Item3.self]
}
@Model class Item1 {
var name: String = ""
var date: Date = Date()
init(name: String) {
self.name = name
self.date = Date()
}
}
@Model class Item2 {
var name: String = ""
var value: Int = 0
init(name: String, value: Int) {
self.name = name
self.value = value
}
}
@Model class Item3 {
var name: String = ""
var text: String = ""
init(name: String, text: String) {
self.name = name
self.text = text
}
}
}
My experiment was:
To create Items for every version of the schema
Updating the typealias along the way to reflect the latest Item version.
Updating the Schema in my ModelContainer to reflect the latest Schema Version.
By AppSchemaV4, I have expected all my Items to be displayed/migrated to Item3, but it does not seem to be the case.
I can only see newly created Item3 records.
My question is, is there something wrong with how I'm doing the migrations? or are migrations not really working with CloudKit right now?
Hi,
Before the iOS 17.2 update the saving behavior of SwiftData was very straightforward, by default it saves to persistence storage and can be configured to save in memory only. Now it saves to memory by default and to make it save to persistence storage we need to use modelContext.Save(). But if we don't quit the App the changes will be saved after a while to persistence storage even without running modelContext.Save() ! How confusing can that be for both developer and the user ! Am I missing something here ?
--
Kind Regards
Accessing a directory on my custom distributed filesystem results in a kernel panic.
According to the backtrace, the last function called before the panic is triggered is mac_label_verify().
See the backtrace file attached.
mac_label_verify-panic.txt
The panic manifests itself given the following conditions:
Machine-a: make a directory in Finder.
Machine-b: remove the directory created on machine-a in Finder.
Machine-a: access the directory removed on machine-b in Finder. Kernel panic ensues.
The panic is reproducible on both Apple Silicon and x86-64.
The backtrace is for x86-64 as I wasn't able to symbolicate it on Apple Silicon.
Not sure how to tackle this one.
Any pointers would be much appreciated.
I am looking for inputs to better understand MacOS entitlements. I ask this in context of OpenJDK project, which builds and ships the JDK. The build process makes uses of make tool and thus doesn't involving building through the XCode product. The JDK itself is a Java language platform providing applications a set of standard APIs. The implementation of these standard APIs internally involves calling platform specific native library functions. In this discussion, I would like to focus on the networking functions that the implementation uses. Almost all of these networking functions and syscalls that the internal implementation uses are BSD socket related. Imagine calls to socket(), connect(), getsockopt(), setsockopt(), getaddrinfo(), sendto(), listen(), accept() and several such.
The JDK that's built through make is then packaged and made available for installation. The packaging itself varies, but for this discussion, I'll focus on the .tar.gz archived packaging. Within this archive there are several executables (for example: java, javac and others) and several libraries. My understanding, based on what I have read of MacOS entitlements is that, the entitlements are set on the executable and any libraries that would be loaded and used by that executable will be evaluated against the entitlements of the executable (please correct me if I misunderstand).
Reading through the list of entitlements noted here https://developer.apple.com/documentation/bundleresources/entitlements, the relevant entitlements that an executable (like "java") which internally invokes BSD socket related syscalls and library functions, appear to be:
com.apple.security.network.client - https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.security.network.client
com.apple.security.network.server - https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.security.network.server
com.apple.developer.networking.multicast - https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.networking.multicast
Is my understanding correct that these are the relevant ones for MacOS? Are there any more entitlements that are of interest? Would it then mean that the executables (java for example) would have to enroll for these entitlements to be allowed to invoke those functions at runtime?
Reading through https://developer.apple.com/documentation/bundleresources/entitlements, I believe that even when an executable is configured with these entitlements, when the application is running if that executable makes use of any operations for which it has an entitlement, the user is still prompted (through a UI notification) whether or not to allow the operation. Did I understand it right?
The part that isn't clear from that documentation is, if the executable hasn't been configured with a relevant entitlement, what happens when the executable invokes on such operation. Will the user see a UI notification asking permission to allow the operation (just like if an entitlement was configured)? Or does that operation just fail in some behind the scenes way?
Coming back to the networking specific entitlements, I found a couple of places in the MacOS documentation where it is claimed that the com.apple.developer.networking.multicast entitlement is only applicable on iOS. In fact, the entitlement definition page for it https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.networking.multicast says:
"Your app must have this entitlement to send or receive IP multicast or broadcast on iOS. It also allows your app to browse and advertise arbitrary Bonjour service types."
Yet, that same page, a few lines above, shows "macOS 10.0+". So, is com.apple.developer.networking.multicast entitlement necessary for an executable running on MacOS which deals with multicasting using BSD sockets?
As a more general comment about the documentation, I see that the main entitlements page here https://developer.apple.com/documentation/bundleresources/entitlements categorizes some of these entitlements under specific categories, for example, notice how some entitlements are categorized under "App Clips". I think it would be useful if there was a category for "BSD sockets" and under that it would list all relevant entitlements that are applicable, even if it means repeating the entitlement names across different categories. I think that will make it easier to identify the relevant entitlements.
Finally, more as a long term question, how does one watch or keep track of these required entitlements for these operations. What I mean is, is it expected that application developers keep visiting the macos documentation, like these pages, to know that a new entitlement is now required in a new macos (update) release? Or are there other ways to keep track of it? For example, if a newer macos requires a new entitlement, then when (an already built) executable is run on that version of macos, perhaps generate a notification or some kind of explicit error which makes it clear what entitlement is missing? I have read through https://developer.apple.com/documentation/bundleresources/diagnosing-issues-with-entitlements but that page focuses on identifying such issues when a executable is being built and doesn't explain the case where an executable has already been shipped with X entitlements and a new Y entitlement is now required to run on a newer version of macos.
I'm facing an issue where if a WiFi network is turned off and back on within a short time frame (2-4 seconds), iOS still shows the device as connected but does not send a new DHCP request. This causes a problem for my network device, which relies on the DHCP request to assign an IP address. Without the request, the device is unable to establish a socket connection properly.
Is there any way to force iOS to send a DHCP request immediately when reconnecting to the network in this scenario? Are there any known workarounds or configurations that might help ensure the DHCP process is re-triggered?
Any insights would be appreciated. Thanks!
I am using es_new_client and es_subscribe in SystemExtension and EndpointSecurity.
I tested it on M3, and it is working. It also works on M1 versions 12, 13, and 14. Additionally, ES_EVENT_TYPE_NOTIFY_KEXTUNLOAD is functioning correctly.
However, there is a bug on M1 Big Sur where es_new_client's es_handler_block_t cannot receive ES_EVENT_TYPE_NOTIFY_KEXTLOAD.
The tested command is:
sudo kextload /System/Library/Extensions/msdosfs.kext
sudo kextload /System/Library/Extensions/*.kext
Is this intended behavior or a bug? Are there any plans to fix it?
Dear Apple:
We found that after mirroring an iPhone and a Mac, calling the NEHotspotConfigurationManager applyConfiguration interface on the iPhone fails to connect to Wi-Fi. Are there any restrictions on using this interface in mirror mode?
We've observed intermittent crashes in our production environment, pls help to take a look at this, thx
https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentrequest/3955945-multitokencontexts
According to this document, I know that I can initialize a multiTokenContexts when initializing ApplePayPaymentRequest.
But I am now facing a tricky problem. If the user's order does not require multiTokenContexts, then I will not initialize this field when I first make ApplePayPaymentRequest. When the user is in the payment process, I may update multiTokenContexts. But this time, the update is not allowed, ApplePay will be cancelled and the payment will be closed.
For example, if the user's address in Apple Pay is different, I need to update multiTokenContexts to support the payment of goods to multiple merchants, which will generate an update of multiTokenContexts. MultiTokenContexts can be updated in the onshippingcontactselected method.
https://developer.apple.com/documentation/apple_pay_on_the_web/applepaysession/1778009-onshippingcontactselected
My question is that from the beginning, there was no multiTokenContexts to update multiTokenContexts in onshippingcontactselected, which would cause the user to close the payment and need to manually click to pay again.
This user experience is not very friendly. Is there a better way for me to go from no multiTokenContexts to multiTokenContexts without interrupting the user's payment process?
Hi
we want to use CONNECT-IP extension within the MASQUE protocol suite.
we want to be able to reroute ICMP packets from our machine and redirect them to our MASQUE proxy.
we want to avoid a creation of virtual interface or modifying the routing tables.
is it possible, if so, how can it be achieved.
thanks
**
Every time after I downloaded an app this window opens and never closes how to close it?**
Topic:
App & System Services
SubTopic:
Hardware
I want a user to be able to save a URL of a folder on a cloud share using the standard FileManager APIs. I'm testing with Dropbox in particular. The initial interaction is working (I can select a folder, save it as a bookmark, and scan the files in that folder). However, no changes made externally to the folder will be reflected in the app when I refresh the contents.
Launching the Files app and browsing to the folder DOES show the updated contents, and once that step is complete, then my app will again show up-to-date contents.
Is there perhaps some API I should be calling to trigger the Dropbox File Provider extension to update it's cache?
Sample project demonstrating issue: https://github.com/dhennessy/FolderScan
STEPS TO REPRODUCE
Launch the app on a physical device
Tap Choose, browse to folder on a Dropbox share, tap Open to select
The app will show the contents of the folder (the 'test' folder)
Switch to the Dropbox app and create a new subfolder of the test folder
Return to the test app and tap Refresh. Notice that the changes do not appear
Re-launching the app also does not show the changes
Workaround
Launch the Files app (or re-open the UIDocumentPickerViewController by tapping choose and then dismiss it)
Tap Refresh and the changes will appear in the app
Note: None of the other 'cloud file providers' (google drive, one drive, box) even allow the user to even select a folder.
if it set com.apple.CoreData.ConcurrencyDebug 1 as launch arg the app always crashes and i cant proceed into the app. is there a way to only raise a warning for these issues so that i can go into the app and check every place in one session for coredata errors?
Hello,
I tried to validate if my app was properly syncing to the cloud. To test this, I created some data in the app, and then deleted the app, and reinstalled. I was expecting the data to still exist but it isn't. Is this a valid test or is the data expected to be deleted when app is deleted?
Topic:
App & System Services
SubTopic:
iCloud & Data
I have a SwiftData application that is using CloudKit. If user is on new device. How can I check and fetch data, instead of just waiting for it happen on its own randomly?
For example, I have onboarding which I do not want user to go through again if they already have an active installation.
Seems like SwiftData is severely limited in pretty much every way, specially any useful CloudKit debugging or control functionality.
I implemented this to receive updates for specific data types and keep the latest daily information up to date. However, for some reason, it only works for a while before stopping completely.
Background Delivery
internal func backgroundDeliveryForReadTypes(enable: Bool, types: Set<HKQuantityType>) async {
do {
if enable {
try await statusForAuthorizationRequest(toWrite: [], toRead: types)
for type in types {
try await healthStore.enableBackgroundDelivery(for: type, frequency: .daily)
}
} else {
for type in types {
try await healthStore.disableBackgroundDelivery(for: type)
}
}
} catch {
debugPrint("Error enabling background delivery: \(error.localizedDescription)")
}
}
HKQueryAnchor
internal var walkingActivityQueryAnchor: HKQueryAnchor? {
get {
if let anchorData = UserDefaults.standard.data(forKey: "walkingActivityAnchor") {
return try? NSKeyedUnarchiver.unarchivedObject(ofClass: HKQueryAnchor.self, from: anchorData)
}
return nil
}
set {
if let newAnchor = newValue {
let anchorData = try? NSKeyedArchiver.archivedData(withRootObject: newAnchor, requiringSecureCoding: true)
UserDefaults.standard.set(anchorData, forKey: "walkingActivityAnchor")
} else {
UserDefaults.standard.removeObject(forKey: "walkingActivityAnchor")
}
}
}
HKAnchoredObjectQuery
internal func observeWalkingActivityInBackground(
_ start: Bool,
toRead: Set<HKQuantityType>,
completion: @escaping @Sendable (Result<WalkingActivityData?, Error>) -> Void
) {
if start {
guard (walkingActivityQuery == nil) else {
return
}
let predicate = getPredicate(date: Date())
let queryDescriptors = toRead.map {
HKQueryDescriptor(sampleType: $0, predicate: predicate)
}
let handleSamples: @Sendable (HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, Error?) -> Void = { [weak self] _, samples, _, newAnchor, error in
guard let self = self else { return }
if let error = error {
completion(.failure(error))
return
}
guard let samples = samples, !samples.isEmpty else {
completion(.success(nil))
return
}
Task {
self.walkingActivityQueryAnchor = newAnchor
let activity = await self.getWalkingActivity(date: Date())
completion(.success(activity))
}
}
let query = HKAnchoredObjectQuery(
queryDescriptors: queryDescriptors,
anchor: walkingActivityQueryAnchor,
limit: HKObjectQueryNoLimit,
resultsHandler: handleSamples
)
query.updateHandler = handleSamples
healthStore.execute(query)
walkingActivityQuery = query
} else {
if let query = walkingActivityQuery {
healthStore.stop(query)
walkingActivityQuery = nil
}
}
}
WalkingActivityData
private func getWalkingActivity(date: Date) async -> WalkingActivityData {
async let averageHeartRate = try await self.getAverageHeartRate(date: date)
async let steps = try self.getStepCount(date: date)
async let durationMinutes = try self.getTotalDurationInMinutes(date: date)
async let distanceMeters = try self.getDistanceWalkingRunning(date: date, unit: .meter())
async let activeCalories = try self.getActiveEnergyBurned(date: date)
return await WalkingActivityData(
date: date,
steps: try? steps,
activeCalories: try? activeCalories,
distanceMeters: try? distanceMeters,
durationMinutes: try? durationMinutes,
averageHeartRate: try? averageHeartRate
)
}
Example of getAverageHeartRate
func getAverageHeartRate(date: Date) async throws -> Double? {
let type = HKQuantityType(.heartRate)
_ = try checkAuthorizationStatus(for: type)
guard let heartRate = try await getDescriptor(
date: date,
type: type,
options: .discreteAverage
).result(for: healthStore)
.statistics(for: date)?
.averageQuantity()?.doubleValue(for: HKUnit.count().unitDivided(by: HKUnit.minute()))
else {
return nil
}
return Double(String(format: "%.2f", heartRate)) ?? 0.0
}
Descriptor & predicate
internal func getPredicate(startDate: Date, endDate: Date) -> NSCompoundPredicate {
let predicateForSamples = HKQuery.predicateForSamples(withStart: startDate, end: endDate)
let excludeManual = NSPredicate(format: "metadata.%K != YES", HKMetadataKeyWasUserEntered)
return NSCompoundPredicate(andPredicateWithSubpredicates: [predicateForSamples, excludeManual])
}
internal func getDescriptor(startDate: Date, endDate: Date, type: HKQuantityType, options: HKStatisticsOptions) -> HKStatisticsCollectionQueryDescriptor {
let calendar = Calendar(identifier: .gregorian)
let anchorDate = calendar.date(bySetting: .hour, value: 0, of: startDate)!
var interval = DateComponents()
interval.day = 1
return HKStatisticsCollectionQueryDescriptor(
predicate: HKSamplePredicate.quantitySample(type: type, predicate: getPredicate(startDate: startDate, endDate: endDate)),
options: options,
anchorDate: anchorDate,
intervalComponents: interval
)
}
Implementation
public func observeWalkingActivityInBackground(_ start: Bool, toRead: Set<HKQuantityType>, memberID: String) {
observeWalkingActivityInBackground(start, toRead: toRead) { [weak self] result in
guard let self = self else { return }
}
}
I am implementing a custom migration, and facing an issue while implementing a WAL checkpointing.
Here is the code for WAL checkpointing
func forceWALCheckpointingForStore(at storeURL: URL, model: NSManagedObjectModel) throws {
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
let options = [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
let store = try persistentStoreCoordinator.addPersistentStore(type: .sqlite, at: storeURL, options: options)
try persistentStoreCoordinator.remove(store)
}
When the coordinator tries to add the store I am getting the following error
fault: Store opened without NSPersistentHistoryTrackingKey but previously had been opened with NSPersistentHistoryTrackingKey - Forcing into Read Only mode store
My questions are
Is it really necessary to force WAL checkpointing before migration? I am expecting NSMigrationManager to handle it internally. I am assuming this because the migrateStore function asks for the sourceType where I am passing StoreType.sqlite
If checkpointing is required, then how do I address the original issue
Note:
Since my app supports macOS 13, I am not able to use the newly introduced Staged migrations.
There is similar question on Stackoverflow that remains unanswered. https://stackoverflow.com/q/69131577/1311902
I have a command line utility I wrote that has been working great up until Sequoia that reads the macro keys from a Logitech G600 gaming mouse and turns it in to custom commands. it was using the following code, checking if usage was 0x80:
IOHIDManagerRegisterInputValueCallback(
g600HIDManager,
{ _, returnResult, callbackSender, valueRef in
let elem = IOHIDValueGetElement(valueRef)
let usage = IOHIDElementGetUsage(elem)
let pressed = IOHIDValueGetIntegerValue(valueRef)
Now i'm having issues with opening the HID manager:
IOHIDManagerOpen(g600HIDManager, IOOptionBits.zero)
After changing the system security from permissive to restrictive, It's giving the error code 0xE00002E2, or no permission. I can't easily add the sandbox entitlements as this is just a simple CLI application, not a bundled app, and even after setting back to csrutil disable, i'm still getting this error.
So now i'm trying to turn it in to a bundled app and use CoreHID instead. Unfortunately I'm not getting any notifications that aren't the mouse itself. From the above code that was working before, i was looking for usage values of 0x80. I'm guessing that directly corresponds to the usage 0x80 in the HID descriptor. I am receiving notifications via
await deviceClient!.monitorNotifications(reportIDsToMonitor: [] , elementsToMonitor: [] )
which should pick up everything for the device. I know the usage i'm looking for is referenced in the device client because it's in the deviceClient.elements collection.
So is there something in CoreHID that specifically blocks Vendor specified Usage pages from being picked up by notifications?
I've also tried just requesting the elements using
let elemToMon = await deviceClient?.elements.filter({ ele in
return ele.usage.page == 0xFF80 && ele.usage.usage == 0x80
})
let request = HIDDeviceClient.RequestElementUpdate(elements: elemToMon!)
let results = await deviceClient!.updateElements([request])
but that call errors (still trying to figure out exactly how it errors).
Any help would be appreciated, either in figuring out why i'm not getting the HID reports in question using CoreHID, or even what has changed that is causing me to not be able to use IOKit.hid anymore.
Thanks in advance!
For reference, here's the decoded HID descriptor:
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x85, 0x01, // Report ID (1)
0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0xE0, // Usage Minimum (0xE0)
0x29, 0xE7, // Usage Maximum (0xE7)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x08, // Report Size (8)
0x95, 0x05, // Report Count (5)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xA4, 0x00, // Logical Maximum (164)
0x19, 0x00, // Usage Minimum (0x00)
0x2A, 0xA4, 0x00, // Usage Maximum (0xA4)
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0x06, 0x80, 0xFF, // Usage Page (Vendor Defined 0xFF80)
0x09, 0x80, // Usage (0x80)
0xA1, 0x01, // Collection (Application)
0x85, 0x80, // Report ID (-128)
0x09, 0x80, // Usage (0x80)
0x75, 0x08, // Report Size (8)
0x95, 0x05, // Report Count (5)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x85, 0xF6, // Report ID (-10)
0x09, 0xF6, // Usage (0xF6)
0x75, 0x08, // Report Size (8)
0x95, 0x07, // Report Count (7)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x85, 0xF0, // Report ID (-16)
0x09, 0xF0, // Usage (0xF0)
0x95, 0x03, // Report Count (3)
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF1, // Report ID (-15)
0x09, 0xF1, // Usage (0xF1)
0x95, 0x07, // Report Count (7)
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF2, // Report ID (-14)
0x09, 0xF2, // Usage (0xF2)
0x95, 0x04, // Report Count (4)
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF3, // Report ID (-13)
0x09, 0xF3, // Usage (0xF3)
0x95, 0x99, // Report Count (-103)
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF4, // Report ID (-12)
0x09, 0xF4, // Usage (0xF4)
0x95, 0x99, // Report Count (-103)
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF5, // Report ID (-11)
0x09, 0xF5, // Usage (0xF5)
0x95, 0x99, // Report Count (-103)
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF6, // Report ID (-10)
0x09, 0xF6, // Usage (0xF6)
0x95, 0x07, // Report Count (7)
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF7, // Report ID (-9)
0x09, 0xF7, // Usage (0xF7)
0x75, 0x08, // Report Size (8)
0x95, 0x1F, // Report Count (31)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
Perhaps I just have the wrong expectations, but I discovered some odd behavior from SwiftData that sure seems like a bug to me...
If you make any change to any SwiftData model object — even just setting a property to its current value — every SwiftUI view that uses SwiftData is rebuilt. Every query and every entity reference, even if the property was set on a model class that is completely unrelated to the view.
SwiftUI does such a good job of optimizing UI updates that it's hard to notice the issue. I only noticed it because the updates were triggering my debug print statements.
To double-check this, I went back to Apple's new iOS app template — the one that is just a list of dated items — and added a little code to touch an unrelated record in the background:
@Model
class UnrelatedItem {
var name: String
init(name: String) {
self.name = name
}
}
@main
struct jumpyApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Item.self,
UnrelatedItem.self
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
init() {
let context = sharedModelContainer.mainContext
// Create 3 items at launch so we immediately have some data to work with.
if try! context.fetchCount(FetchDescriptor<Item>()) == 0 {
for _ in 0..<3 {
let item = Item(timestamp: Date())
context.insert(item)
}
}
// Now create one unrelated item.
let unrelatedItem = UnrelatedItem(name: "Mongoose")
context.insert(unrelatedItem)
try? context.save()
// Set up a background task that updates the unrelated item every second.
Task {
while true {
try? await Task.sleep(nanoseconds: 1_000_000_000)
Task { @MainActor in
// We don't even have to change the name or save the contxt.
// Just setting the name to the same value will trigger a change.
unrelatedItem.name = "Mongoose"
}
}
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
I also added a print statement to the ContentView so I could see when the view updates.
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
var body: some View {
NavigationSplitView {
List {
let _ = Self._printChanges()
...
The result is that the print statement logs 2 messages to the debug console every second. I checked in iOS 17, 18.1, and 18.2, and they all behave this way.
Is this the intended behavior? I thought the whole point of the new Observation framework in iOS 17 was to track which data had changed and only send change notifications to observers who were using that data.