用测试数据填充健康应用程序的理想方式

Ideal way to fill Health app with testing data

我知道我可以独立地为所有数据写入 HealthKit,但我想知道是否有官方方法可以用假数据填充 Health 应用程序以进行调试。我查看了所有文档但找不到任何内容,我只想在从应用程序编写它之前先确定一下。 谢谢!

目前还没有官方的方法来创建假数据集。

您可以使用这个项目来帮助您获取测试数据: https://github.com/Comocomo/HealthKitImporter/

它基本上实现了健康工具包写入方法,以将数据从 export.xml 文件(从包含 healthkit 数据的设备导出)

插入到您的设备/模拟器健康应用程序中

正在将export.xml文件加载到项目中并通过调用解析它:

func start() {
        dataImporter = HKimporter {
            if let path = Bundle.main.url(forResource: "export", withExtension: "xml") {
                if let parser = XMLParser(contentsOf: path) {
                    parser.delegate = self.dataImporter
                    parser.parse()
                    self.dataImporter.saveAllSamples()
                }
            } else {
                print ("file not found")
            }
        }

漂亮地打印记录

extension CustomStringConvertible {
    var description : String {
        var description: String = ""
        //if self is AnyObject {
        //    description = "***** \(type(of: self)) - <\(unsafeAddressOf((self as AnyObject)))>***** \n"
        //} else {
        description = "***** \(type(of: self)) *****\n"
        //}
        let selfMirror = Mirror(reflecting: self)
        for child in selfMirror.children {
            if let propertyName = child.label {
                description += "\(propertyName): \(child.value)\n"
            }
        }
        return description
    }
}

用于读取 XML 记录并保存 HK 样本的 HKImporter

import UIKit
import HealthKit

class HKRecord: CustomStringConvertible {
    var type: String = String()
    var value: Double = 0
    var unit: String?
    var sourceName: String = String()
    var sourceVersion: String = String()
    var startDate: Date = Date()
    var endDate: Date = Date()
    var creationDate: Date = Date()

    //for workouts
    var activityType: HKWorkoutActivityType? = HKWorkoutActivityType(rawValue: 0)
    var totalEnergyBurned: Double = 0
    var totalDistance: Double = 0
    var totalEnergyBurnedUnit: String = String()
    var totalDistanceUnit: String = String()

    var metadata: [String:String]?
}

class HKimporter : NSObject, XMLParserDelegate {

    var healthStore:HKHealthStore?

    var allHKRecords: [HKRecord] = []
    var allHKSampels: [HKSample] = []

    var eName: String = String()
    var currRecord: HKRecord = HKRecord.init()

    var readCounterLabel: UILabel? = nil
    var writeCounterLabel: UILabel? = nil


    convenience init(completion:@escaping ()->Void) {

        self.init()

        self.healthStore = HKHealthStore.init()

        let shareReadObjectTypes:Set<HKSampleType>? = [
            HKQuantityType.quantityType(forIdentifier:HKQuantityTypeIdentifier.stepCount)!,
            HKQuantityType.quantityType(forIdentifier:HKQuantityTypeIdentifier.flightsClimbed)!,
            // Body Measurements
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.height)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyFatPercentage)!,
            // Nutrient
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryProtein)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryFatTotal)!,
            //                        HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryFatSaturated)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryCarbohydrates)!,
            //                        HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietarySugar)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryEnergyConsumed)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bloodGlucose)!,
            // Fitness
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.activeEnergyBurned)!,
            HKWorkoutType.workoutType(),
            // Category
            HKQuantityType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!,

            //Heart rate
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRateVariabilitySDNN)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.restingHeartRate)!,

            // Measurements
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyFatPercentage)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.height)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.leanBodyMass)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMassIndex)!,
            // Nutrients
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryFatTotal)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryFatPolyunsaturated)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryFatMonounsaturated)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryFatSaturated)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryCholesterol)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietarySodium)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryCarbohydrates)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryFiber)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietarySugar)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryEnergyConsumed)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryProtein)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryVitaminA)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryVitaminB6)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryVitaminB12)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryVitaminC)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryVitaminD)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryVitaminE)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryVitaminK)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryCalcium)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryIron)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryThiamin)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryRiboflavin)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryNiacin)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryFolate)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryBiotin)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryPantothenicAcid)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryPhosphorus)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryIodine)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryMagnesium)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryZinc)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietarySelenium)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryCopper)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryManganese)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryChromium)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryMolybdenum)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryChloride)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryPotassium)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryCaffeine)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryWater)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.uvExposure)!,
            // Fitness
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.distanceWalkingRunning)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.distanceCycling)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.basalEnergyBurned)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.activeEnergyBurned)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.flightsClimbed)!,
            // Results
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyTemperature)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bloodPressureSystolic)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bloodPressureDiastolic)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.respiratoryRate)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.basalBodyTemperature)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bloodGlucose)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.oxygenSaturation)!,
            HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bloodAlcoholContent)!]

        self.healthStore?.requestAuthorization(toShare: shareReadObjectTypes, read: shareReadObjectTypes, completion: { (res, error) in
            if let error = error {
                print(error)
            } else {
                completion()
            }
        })
    }

    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
        eName = elementName
        if elementName == "Record" {
            currRecord.type = attributeDict["type"]!
            currRecord.sourceName = attributeDict["sourceName"] ??  ""
            currRecord.sourceVersion = attributeDict["sourceVersion"] ??  ""
            currRecord.value = Double(attributeDict["value"] ?? "0") ?? 0
            currRecord.unit = attributeDict["unit"] ?? ""

            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd hh:mm:ss Z"
            if let date = formatter.date(from: attributeDict["startDate"]!) {
                currRecord.startDate = date
            }
            if let date = formatter.date(from: attributeDict["endDate"]!){
                currRecord.endDate = date
            }

            if currRecord.startDate >  currRecord.endDate {
                currRecord.startDate = currRecord.endDate
            }

            if let date = formatter.date(from: attributeDict["creationDate"]!){
                currRecord.creationDate = date
            }
        } else if elementName == "MetadataEntry" {
            currRecord.metadata = attributeDict
        } else if elementName == "Workout" {
            print(attributeDict)
            currRecord.type = HKObjectType.workoutType().identifier
            currRecord.activityType = activityByName(activityName: attributeDict["workoutActivityType"] ?? "")
            currRecord.sourceName = attributeDict["sourceName"] ??  ""
            currRecord.sourceVersion = attributeDict["sourceVersion"] ??  ""
            currRecord.value = Double(attributeDict["duration"] ?? "0") ?? 0
            currRecord.unit = attributeDict["durationUnit"] ?? ""
            currRecord.totalDistance = Double(attributeDict["totalDistance"] ?? "0") ?? 0
            currRecord.totalDistanceUnit = attributeDict["totalDistanceUnit"] ??  ""
            currRecord.totalEnergyBurned = Double(attributeDict["totalEnergyBurned"] ?? "0") ?? 0
            currRecord.totalEnergyBurnedUnit = attributeDict["totalEnergyBurnedUnit"] ??  ""

            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd hh:mm:ss Z"
            if let date = formatter.date(from: attributeDict["startDate"]!) {
                currRecord.startDate = date
            }
            if let date = formatter.date(from: attributeDict["endDate"]!){
                currRecord.endDate = date
            }

            if currRecord.startDate >  currRecord.endDate {
                currRecord.startDate = currRecord.endDate
            }

            if let date = formatter.date(from: attributeDict["creationDate"]!){
                currRecord.creationDate = date
            }
        } else if elementName == "Correlation" {
            return
        } else {
            return
        }
    }

    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        if elementName == "Record" || elementName == "Workout" {

            allHKRecords.append(currRecord)
            print(currRecord.description)
            DispatchQueue.main.async {
                self.readCounterLabel?.text = "\(self.allHKRecords.count)"
            }
            saveHKRecord(item: currRecord, withSuccess: {
                // success
                //print("record added to array")
            }, failure: {
                // fail
                print("fail to process record")
            })
        }
    }

    func saveHKRecord(item:HKRecord, withSuccess successBlock: @escaping () -> Void, failure failiureBlock: @escaping () -> Void) {

        let unit = HKUnit.init(from: item.unit!)
        let quantity = HKQuantity(unit: unit, doubleValue: item.value)

        var hkSample: HKSample? = nil
        if let type = HKQuantityType.quantityType(forIdentifier:  HKQuantityTypeIdentifier(rawValue: item.type)) {
            hkSample = HKQuantitySample.init(type: type, quantity: quantity, start: item.startDate, end: item.endDate, metadata: item.metadata)
        } else if let type = HKCategoryType.categoryType(forIdentifier: HKCategoryTypeIdentifier(rawValue: item.type)) {
            hkSample = HKCategorySample.init(type: type, value: Int(item.value), start: item.startDate, end: item.endDate, metadata: item.metadata)
        } else if item.type == HKObjectType.workoutType().identifier {
            hkSample = HKWorkout.init(activityType: HKWorkoutActivityType(rawValue: 0)!, start: item.startDate, end: item.endDate, duration: item.value, totalEnergyBurned: HKQuantity(unit: HKUnit.init(from: item.totalEnergyBurnedUnit), doubleValue: item.totalEnergyBurned), totalDistance: HKQuantity(unit: HKUnit.init(from: item.totalDistanceUnit), doubleValue: item.totalDistance), device: nil, metadata: item.metadata)
        } else {
            print("didnt catch this item - \(item)")
        }

        if let hkSample = hkSample, (self.healthStore?.authorizationStatus(for: hkSample.sampleType) == HKAuthorizationStatus.sharingAuthorized) {
            allHKSampels.append(hkSample)
            successBlock()
        } else {
            failiureBlock()
        }
    }

    func saveAllSamples() {
        saveSamplesToHK(samples: self.allHKSampels, withSuccess: {
            //
        }, failure: {
            //
        })
    }
    func saveSamplesToHK (samples:[HKSample], withSuccess successBlock: @escaping () -> Void, failure failiureBlock: @escaping () -> Void) {
        self.healthStore?.save(samples, withCompletion: { (success, error) in
            if (!success) {
                print(String(format: "An error occured saving the sample. The error was: %@.", error.debugDescription))
                failiureBlock()
            }
            DispatchQueue.main.async {
                self.writeCounterLabel?.text = "\(Int((self.writeCounterLabel?.text)!)! + samples.count)"
            }
            successBlock()
        })
    }

    func activityByName(activityName: String) -> HKWorkoutActivityType {
        var res = HKWorkoutActivityType(rawValue: 0)
        switch activityName {
        case "HKWorkoutActivityTypeWalking":
            res = HKWorkoutActivityType.walking
        case "HKWorkoutActivityTypeRunning":
            res = HKWorkoutActivityType.running
        case "HKWorkoutActivityTypeCycling":
            res = HKWorkoutActivityType.cycling
        case "HKWorkoutActivityTypeMixedMetabolicCardioTraining":
            res = HKWorkoutActivityType.mixedMetabolicCardioTraining
        case "HKWorkoutActivityTypeYoga":
            res = HKWorkoutActivityType.yoga
        case "HKWorkoutActivityTypeFunctionalStrengthTraining":
            res = HKWorkoutActivityType.functionalStrengthTraining
        case "HKWorkoutActivityTypeTraditionalStrengthTraining":
            res = HKWorkoutActivityType.traditionalStrengthTraining
        case "HKWorkoutActivityTypeDance":
            res = HKWorkoutActivityType.dance
        default:
            print ("???????")
            print ("Add support for activity - \(activityName)")
            break;
        }
        return res!
    }
}

这将处理大多数常见的 HK 记录和类型,但不是全部,如果您有缺少类型的导出文件,您可以在代码中自行添加它们。