CKSubscription returns error that query must have at least one firing mode when using FiresOnce - swift

I have this code. Which works when creating a subscription with a firing mode of FiresOnRecordCreation... but getting error on FiresOnce.. the error is "Query Subscriptions must have at least one type of firing mode"
My goal is that I have a photo. When the first rating of the photo occurs i want to get a notification. I do not want to use FiresOnRecordCreation on the reference as for every rating there will be a notification which is too many. I just want the first notification for the first rating received.
let database = CKContainer.defaultContainer().publicCloudDatabase
let predicate = NSPredicate(format:"owningPhoto == %#", ref)
let subscription = CKSubscription(recordType: "PhotoRatings", predicate: predicate, options: .FiresOnce)
xcode crashes on the subscription but on with FiresOnce.

besides the .FireOnce you should also indicate if that's for creation, update or deletion. So the call should be something like:
let subscription = CKSubscription(recordType: "PhotoRatings", predicate: predicate, options: [.FiresOnce, .FiresOnRecordCreation, .FiresOnRecordUpdate, .FiresOnRecordDeletion])

Related

Health Kit: How to set 'totalEnergyBurned' for workout?

Today I worked for the first time with Apple Health Kit and successfully saved a workout in Health with the basic informations (activityType, start and end).
My app is in basic function an interval timer where you can create your own workout. Now in the next step I want to add the calories burned in the workout. This information is stored in the attribute 'totalEnergyBurned'.
Do I need to calculate this value myself or can I query this value directly if the user is wearing an Apple Watch? Or maybe the value is even automatically added to the workout if there is the corresponding record? (So far I have only tested the app in the simulator, which is why I can't answer this possibility).
My current code:
func saveToHealthKit(entryID: String){
if HKHealthStore.isHealthDataAvailable() {
let healthStore = HKHealthStore()
if(healthStore.authorizationStatus(for: HKObjectType.workoutType()) == .sharingAuthorized && healthStore.authorizationStatus(for: HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.activeEnergyBurned)!) == .sharingAuthorized){
let newWorkout = HKWorkout(activityType: HKWorkoutActivityType.highIntensityIntervalTraining, start: entry.date!, end: Date())
healthStore.save(newWorkout) { success, error in
guard success else {
// Perform proper error handling here.
return
}
// Add detail samples here.
}
}
}
}
}
Thanks :)
I am not sure I understood your case correctly, but if the user is using an Apple Watch and does a workout you can query HealthKit for an HKWorkout object, which holds a totalEnergyBurned attribute.
If you are not querying this HKWorkout object, then I believe you can query for the activeEnergyBurned quantity type using the start and end date-time of the user workout in the predicate.

func removeFilterPredicate(_ predicate: MPMediaPredicate) not working consistently

I am experiencing an inconsistent issue when trying func removeFilterPredicate(_ predicate: MPMediaPredicate) however I never have an issue when trying func addFilterPredicate(_ predicate: MPMediaPredicate)
When I test my app and add the predicate it works every time without fail. When I try to remove the predicate usually it works, but sometimes it does not.
The behavior I expect is to remove the predicate and then a random song that does not match the previous predicate to play
What is actually sometimes happening is after I remove the predicate and skip to next song more songs from the previous predicate still play
I have found if I want to guarantee it to fail I can do so by adding the predicate to only play songs from a specific Artist and then rapidly skip a few songs and then try to remove the predicate.
The way I add the predicate is
func getSongsWithCurrentArtistFor(item: MPMediaItem) -> MPMediaQuery {
let artistPredicate = MPMediaPropertyPredicate(value: item.artist, forProperty: MPMediaItemPropertyArtist, comparisonType: .contains)
let query = MPMediaQuery()
query.addFilterPredicate(artistPredicate)
return query
}
let lockArtist = MediaManager.shared.getSongsWithCurrentArtistFor(item: nowPlaying)
if var items = lockArtist.items {
items.shuffle()
let descriptor = MPMusicPlayerMediaItemQueueDescriptor(itemCollection: MPMediaItemCollection(items: items))
self.mediaPlayer.prepend(descriptor)
}
Which again NEVER fails
To remove the predicate:
func removeArtistLockFor(item: MPMediaItem) -> MPMediaQuery {
let artistPredicate = MPMediaPropertyPredicate(value: item.artist, forProperty: MPMediaItemPropertyArtist)
let query = MPMediaQuery()
query.removeFilterPredicate(artistPredicate)
return query
}
let unlockArtist = MediaManager.shared.removeArtistLockFor(item: nowPlaying)
if var items = unlockArtist.items?.filter({ (item) -> Bool in
return item.mediaType.rawValue <= MPMediaType.anyAudio.rawValue
}) {
items.shuffle()
let descriptor = MPMusicPlayerMediaItemQueueDescriptor(itemCollection: MPMediaItemCollection(items: self.newSongs.shuffled()))
self.mediaPlayer.prepend(descriptor)
}
I am wondering if A) There is an issue with the way I am removing MPMediaPropertyPredicate, B) I need some sort of added method to fix the edge case of rapidly changing song, or C) both.
I know I have previously posted about issues with MPMediaPlayer .. specifically aboutfunc prepend(_ descriptor: MPMusicPlayerQueueDescriptor) but those issues were resolved in ios13. This is a new issue...and the fact that adding the predicate ALWAYS working makes me think this is more of a mistake on my part and not Apple's issue...?
The lines in your removeArtistLockFor function
let query = MPMediaQuery()
query.removeFilterPredicate(artistPredicate)
...appears to be creating a new instance of MPMediaQuery so unless it behaves like a singleton (which from the documentation doesn't appear to be the case), there will be nothing to remove at this point.
Should you be holding on to a reference to the query from when you add the predicate and pass it in to the remove function so you can then do
passedQuery.removeFilterPredicate(artistPredicate)

How to Fetch Records from CloudKit Not in My Local Cache

I have an app that uses CloudKit for sync and I maintain a local cache of records. I have run into a sync scenario that I can't figure out.
I'm using the Public database and when my app is opened, I want to be able to go get all the updated records that my app missed while it was closed, or on a device where the app was just installed.
I can get the updated records by creating a NSPredicate to compare the modificationDate like this:
let predicate = NSPredicate(format: "modificationDate > %#", syncTimestamp as CVarArg)
let query = CKQuery(recordType: recordType, predicate: predicate)
But the part I can't figure out is how to get only the records that have been added to, or removed from, the CloudKit server.
Does anyone know how to do this? I know Apple provides a way for this in the Private database but I'm using the Public one here.
The only thing I can think of so far is to query all the reocrds of a certain recordType, collect all their recordNames and compare to my local cache. But it'd be nice to have smarter way than just pull large amounts of data and comparing huge arrays of recordNames.
CKQuerySubscription(recordType: myRecordType, predicate: predicate, options: [.firesOnRecordCreation, .firesOnRecordDeletion]) works perfectly on public DB.
Here's a code snippet (saving subscription is done with RxCloudKit, but this is beyond the point) -
let predicate = NSPredicate(format: "TRUEPREDICATE")
// the options are different from what you need, just showcasing the possibilities:
let subscription = CKQuerySubscription(recordType: recordTypeTest, predicate: predicate, options: [.firesOnRecordCreation, .firesOnRecordUpdate, .firesOnRecordDeletion, .firesOnce])
let info = CKNotificationInfo()
info.alertLocalizationKey = "NEW_PARTY_ALERT_KEY"
info.soundName = "NewAlert.aiff"
info.shouldBadge = true
subscription.notificationInfo = info
self.publicDB.rx.save(subscription: subscription).subscribe { event in
switch event {
case .success(let subscription):
print("subscription: ", subscription)
case .error(let error):
print("Error: ", error)
default:
break
}
}.disposed(by: self.disposeBag)

swift request coredata and delete

i would like to request all my core data where the "mhd" field >= the date of today. this code works fine:
func DatenAbrufen() {
let calendar = NSCalendar.currentCalendar()
let AktuellesDatum = calendar.startOfDayForDate(NSDate())
let fetchRequest = NSFetchRequest(entityName: "LM_ITEMS")
fetchRequest.predicate = NSPredicate(format: "mhd >= %#", AktuellesDatum)
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LM_ITEMS] {
lebensmittel = fetchResults
}
}
how can i delete in this query all other data, where "mhd" is not >= date of today?
If you need to support iOS 8.x, or OS X Yosemite, or older, you'll need to fetch each object and then delete it. Write another predicate that is the opposite of the one you have. Execute that fetch request. Then iterate over the found objects and send each one a deleteObject. Save your managed object context. This can take quite a while, if you're deleting lots of objects.
For iOS 9 and El Capitan, you can use NSBatchDeleteRequest, described in WWDC 2015 session 220. For large numbers of deleted objects, NSBatchDeleteRequest is much faster. You're not loading each object into memory just to delete it. You also bypass some validation rules, and must refresh your MOC for it to see changes–which might be undesired consequences, depending on your usage.

CloudKit - CKQueryOperation results different each time the same query is ran

This is the case - I'm using a simple UITableView that renders records from the CloudKit publicDB. When I run the app, the query operation returns for example returns 2 results (that's all it has currently).
My table view has a refresh control and when I pull to refresh I got zero results, if I continue to do reloads, eventually a result might come out but now always.
The same thing happens with more results as well, I used to have CKLocation type which I queried and the response was always different without any common sense
Some example code (the predicate in this case is TRUEPREDICATE - nothing fancy):
let sort = NSSortDescriptor(key: "creationDate", ascending: false)
let query = CKQuery(recordType: "Tests", predicate: DiscoveryMode.getPredicate())
query.sortDescriptors = [sort]
var operation = CKQueryOperation(query: query)
if lastCursor != nil {
operation = CKQueryOperation(cursor: lastCursor)
}
operation.resultsLimit = 15
operation.recordFetchedBlock = recordFetchBlock
operation.queryCompletionBlock = { [weak self] (cursor:CKQueryCursor!, error:NSError!) in
if cursor != nil {
self!.lastCursor = cursor
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
Misc.hideLoadingInView(view: self!.view)
self!.tableView.reloadData()
self!.refreshControl.endRefreshing()
if error != nil {
Misc.showErrorInView(view: self!.view, message: error.localizedDescription)
}
})
}
CloudKit.sharedInstance.publicDB.addOperation(operation)
All the recordFetchBlock does is to add objects to a mutable array that the table view uses as dataSource.
I'm new to CloudKit and I'm puzzled is this by design (not returning all the results but some random) or I'm doing something wrong?
I see that you are using a cursor. because of that the 2nd call will start at the point where the first call ended. You have a resultsLimit of 15. When using a cursor, you will only receive records the 2nd time you execute the query if there were more than 15 records. To test if this is the issue just comment out the line where you set the cursor: operation = CKQueryOperation(cursor: lastCursor)
I found the issue, I was trying to do (in the NSPredicate) a radius with a value I read somewhere is in kilometers. Therefore I was trying to query records within 500 meters instead of 500 kilometers and the GPX file I'm using in the simulator has multiple records with a larger distance. Since it simulates movement, that was the reason not to get consistent results.
Now, when I'm using a proper value for the radius all seems to be just fine!