2015-09-29 2 views
0

Я был рад узнать, что 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 для простоты.

ответ

2

Удалите предикат (предикат: nil), когда вы создаете запрос, и вы увидите больше результатов, включая удаленные результаты. В первый раз, когда вы запустите это (до того, как HKQueryAnchor был сохранен), вы получите все результаты, чтобы вы могли что-то сделать для их фильтрации; но последующее выполнение запроса будет использовать сохраненный якорь, поэтому вы увидите только изменения с сохраненного якоря.

Возможно, вы также захотите установить свойство updateHandler в своем запросе перед выполнением. Это заставит запрос работать постоянно в фоновом режиме, вызывая обработчик обновления всякий раз, когда есть изменения (добавляет или удаляет). Код в конце getGlucoseSinceAnchor (...) выглядит как

... 
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: nil, anchor: hkAnchor, limit: Int(maxResults), resultsHandler: onAnchorQueryResults) 
anchoredQuery.updateHandler = onAnchorQueryResults 
healthKitStore?.executeQuery(anchoredQuery) 
... 
+0

Спасибо. Это сработало, но очень грустно, что я не могу указать предикат, если мне нужны обратные вызовы для удаленных объектов. Это даже терпит неудачу, если я дам только дату начала и дату окончания. Вместо этого я должен использовать предикат nil и получать ВСЕ результаты из всех источников за все время, а затем анализировать после факта. Если предикаты работают только над добавленными записями, это кажется мне нарушенным. – thedigitalsean

+1

При дальнейшем исследовании предикаты для источников работают для удаления. Моя теория такова: getPredicateForSamples для HKSamples по дате. HKDeletedObjects наследуется от HKObject, а не HKSample. Поэтому я подозреваю, что, поскольку фильтры getPredicateForSamples() для HKSamples в диапазоне дат, он пропускает HKDeletedObjects. Теперь, если я фильтрую источники с помощью getPredicateForObjects(), он фильтрует HKObjects, а не HKSamples. Поэтому он возвращает как HKSample, так и HKDeletedObject, поскольку оба этих класса наследуются от HKObject. Мне это не нравится, но, похоже, это происходит. – thedigitalsean

+0

Я собирался углубиться в это немного дальше, когда увидел ваш комментарий. Ваша теория имеет смысл. – steve1951