Я был рад узнать, что Apple добавила отслеживание удалений в HealthKit в iOS 9. Поэтому я создал тестовый проект, чтобы попробовать его. К сожалению, хотя я могу получить новые данные просто отлично, я не получаю никаких удаленных объектов в своих обратных вызовах.Healthkit HKAnchoredObjectQuery в iOS 9 не возвращается HKDeletedObject
У меня есть функция HKAnchoredObjectQuery, которая отслеживает HKQueryAnchor и дает мне новые HKSamples всякий раз, когда я добавляю количество BloodGlucose в HealthKit через приложение Health. Однако, когда я удаляю то же количество и повторно запускаю приложение, HKDeletedObject всегда пуст. Даже я добавляю и удаляю одновременно. Кажется, что независимо от того, что я делаю, массив HKDeletedObject всегда пуст. Но дополнения работают нормально (только получение добавленных образцов с момента последнего привязки).
Вот мой код. Это всего лишь 2 файла. Чтобы воссоздать проект, просто создайте новый быстрый проект, дайте себе право на HealthKit и скопируйте его. (Примечание. Когда вы запускаете его, вы получаете только одно обновление для каждого прогона, поэтому, если вы вносите изменения в HealthKit, вам нужно остановиться и перезапустить приложение, чтобы проверить обратные вызовы)
Это мой HealthKit клиент:.
//
// HKClient.swift
// HKTest
import UIKit
import HealthKit
class HKClient : NSObject {
var isSharingEnabled: Bool = false
let healthKitStore:HKHealthStore? = HKHealthStore()
let glucoseType : HKObjectType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodGlucose)!
override init(){
super.init()
}
func requestGlucosePermissions(authorizationCompleted: (success: Bool, error: NSError?)->Void) {
let dataTypesToRead : Set<HKObjectType> = [ glucoseType ]
if(!HKHealthStore.isHealthDataAvailable())
{
// let error = NSError(domain: "com.test.healthkit", code: 2, userInfo: [NSLocalizedDescriptionKey: "Healthkit is not available on this device"])
self.isSharingEnabled = false
return
}
self.healthKitStore?.requestAuthorizationToShareTypes(nil, readTypes: dataTypesToRead){(success, error) -> Void in
self.isSharingEnabled = true
authorizationCompleted(success: success, error: error)
}
}
func getGlucoseSinceAnchor(anchor:HKQueryAnchor?, maxResults:uint, callback: ((source: HKClient, added: [String]?, deleted: [String]?, newAnchor: HKQueryAnchor?, error: NSError?)->Void)!){
let queryEndDate = NSDate(timeIntervalSinceNow: NSTimeInterval(60.0 * 60.0 * 24))
let queryStartDate = NSDate.distantPast()
let sampleType: HKSampleType = glucoseType as! HKSampleType
let predicate: NSPredicate = HKAnchoredObjectQuery.predicateForSamplesWithStartDate(queryStartDate, endDate: queryEndDate, options: HKQueryOptions.None)
var hkAnchor: HKQueryAnchor;
if(anchor != nil){
hkAnchor = anchor!
} else {
hkAnchor = HKQueryAnchor(fromValue: Int(HKAnchoredObjectQueryNoAnchor))
}
let onAnchorQueryResults : ((HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, NSError?) -> Void)! = {
(query:HKAnchoredObjectQuery, addedObjects:[HKSample]?, deletedObjects:[HKDeletedObject]?, newAnchor:HKQueryAnchor?, nsError:NSError?) -> Void in
var added = [String]()
var deleted = [String]()
if (addedObjects?.count > 0){
for obj in addedObjects! {
let quant = obj as? HKQuantitySample
if(quant?.UUID.UUIDString != nil){
let val = Double((quant?.quantity.doubleValueForUnit(HKUnit(fromString: "mg/dL")))!)
let msg : String = (quant?.UUID.UUIDString)! + " " + String(val)
added.append(msg)
}
}
}
if (deletedObjects?.count > 0){
for del in deletedObjects! {
let value : String = del.UUID.UUIDString
deleted.append(value)
}
}
if(callback != nil){
callback(source:self, added: added, deleted: deleted, newAnchor: newAnchor, error: nsError)
}
}
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: predicate, anchor: hkAnchor, limit: Int(maxResults), resultsHandler: onAnchorQueryResults)
healthKitStore?.executeQuery(anchoredQuery)
}
let AnchorKey = "HKClientAnchorKey"
func getAnchor() -> HKQueryAnchor? {
let encoded = NSUserDefaults.standardUserDefaults().dataForKey(AnchorKey)
if(encoded == nil){
return nil
}
let anchor = NSKeyedUnarchiver.unarchiveObjectWithData(encoded!) as? HKQueryAnchor
return anchor
}
func saveAnchor(anchor : HKQueryAnchor) {
let encoded = NSKeyedArchiver.archivedDataWithRootObject(anchor)
NSUserDefaults.standardUserDefaults().setValue(encoded, forKey: AnchorKey)
NSUserDefaults.standardUserDefaults().synchronize()
}
}
Это мой Вид:
//
// ViewController.swift
// HKTest
import UIKit
import HealthKit
class ViewController: UIViewController {
let debugLabel = UILabel(frame: CGRect(x: 10,y: 20,width: 350,height: 600))
override func viewDidLoad() {
super.viewDidLoad()
self.view = UIView();
self.view.backgroundColor = UIColor.whiteColor()
debugLabel.textAlignment = NSTextAlignment.Center
debugLabel.textColor = UIColor.blackColor()
debugLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping
debugLabel.numberOfLines = 0
self.view.addSubview(debugLabel)
let hk = HKClient()
hk.requestGlucosePermissions(){
(success, error) -> Void in
if(success){
let anchor = hk.getAnchor()
hk.getGlucoseSinceAnchor(anchor, maxResults: 0)
{ (source, added, deleted, newAnchor, error) -> Void in
var msg : String = String()
if(deleted?.count > 0){
msg += "Deleted: \n" + (deleted?[0])!
for s in deleted!{
msg += s + "\n"
}
}
if (added?.count > 0) {
msg += "Added: "
for s in added!{
msg += s + "\n"
}
}
if(error != nil) {
msg = "Error = " + (error?.description)!
}
if(msg.isEmpty)
{
msg = "No changes"
}
debugPrint(msg)
if(newAnchor != nil && newAnchor != anchor){
hk.saveAnchor(newAnchor!)
}
dispatch_async(dispatch_get_main_queue(), {() -> Void in
self.debugLabel.text = msg
})
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Примечание: Я знаю, что компания Apple рекомендует установить это используя HKObserverQuery. Я изначально делал это в проекте Xamarin, и поведение было одинаковым (никаких HKDeletedObjects не отправляли). Поэтому, пытаясь быстро и быстро, я оставил HKObserverQuery для простоты.
Спасибо. Это сработало, но очень грустно, что я не могу указать предикат, если мне нужны обратные вызовы для удаленных объектов. Это даже терпит неудачу, если я дам только дату начала и дату окончания. Вместо этого я должен использовать предикат nil и получать ВСЕ результаты из всех источников за все время, а затем анализировать после факта. Если предикаты работают только над добавленными записями, это кажется мне нарушенным. – thedigitalsean
При дальнейшем исследовании предикаты для источников работают для удаления. Моя теория такова: getPredicateForSamples для HKSamples по дате. HKDeletedObjects наследуется от HKObject, а не HKSample. Поэтому я подозреваю, что, поскольку фильтры getPredicateForSamples() для HKSamples в диапазоне дат, он пропускает HKDeletedObjects. Теперь, если я фильтрую источники с помощью getPredicateForObjects(), он фильтрует HKObjects, а не HKSamples. Поэтому он возвращает как HKSample, так и HKDeletedObject, поскольку оба этих класса наследуются от HKObject. Мне это не нравится, но, похоже, это происходит. – thedigitalsean
Я собирался углубиться в это немного дальше, когда увидел ваш комментарий. Ваша теория имеет смысл. – steve1951