3
\$\begingroup\$

I have 2 asynchronous return values from 2 different classes, one from HealthKit, the other from MotionManager. I combine the outcome of these classes through a combinedViewModel. The code works, but I want to know if it is properly formatted and if all functions, ... are used in the right place. With other words, can I build an official app with this code or do I need to structure it differently.

MotionManager.swift

struct MotionValues {
    var rotationX: Double = 0.0
    var rotationY: Double = 0.0
    var rotationZ: Double = 0.0
    var pitch: Double = 0.0
    var roll: Double = 0.0
    var yaw: Double = 0.0
}  

class MotionManager {

    @Published var motionValues = MotionValues()

    private let manager = CMMotionManager()

    func startMotionUpdates() {
        manager.deviceMotionUpdateInterval = 1.0
        manager.startDeviceMotionUpdates(to: .main) { (data, error) in

            guard let data = data, error == nil else {
                print(error!)
                return
            }

            self.motionValues.rotationX = data.rotationRate.x
            self.motionValues.rotationY = data.rotationRate.y
            self.motionValues.rotationZ = data.rotationRate.z

            self.motionValues.pitch = data.attitude.pitch
            self.motionValues.roll = data.attitude.roll
            self.motionValues.yaw = data.attitude.yaw
        }
    }

    func stopMotionUpdates() {
        manager.stopDeviceMotionUpdates()
        resetAllMotionData()
    }

    func resetAllMotionData() {
        self.motionValues.rotationX = 0.0
        self.motionValues.rotationY = 0.0
        self.motionValues.rotationZ = 0.0
        self.motionValues.pitch = 0.0
        self.motionValues.roll = 0.0
        self.motionValues.yaw = 0.0
    }
}

HealthKitManager.swift

class HealthKitManager {

    private var healthStore = HKHealthStore()
    private var heartRateQuantity = HKUnit(from: "count/min")
    private var activeQueries = [HKQuery]()

    @Published var heartRateValue: Double  = 0.0

    func autorizeHealthKit() {

        let heartRate = HKObjectType.quantityType(forIdentifier: .heartRate)!
        let heartRateVariability = HKObjectType.quantityType(forIdentifier: .heartRateVariabilitySDNN)!

        let HKreadTypes: Set = [heartRate, heartRateVariability]

        healthStore.requestAuthorization(toShare: nil, read: HKreadTypes) { (success, error) in
            if let error = error {
                print("Error requesting health kit authorization: \(error)")
            }
        }
    }

    func fetchHeartRateData(quantityTypeIdentifier: HKQuantityTypeIdentifier ) {

        let devicePredicate = HKQuery.predicateForObjects(from: [HKDevice.local()])
        let updateHandler: (HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, Error?) -> Void = {
        query, samples, deletedObjects, queryAnchor, error in
            guard let samples = samples as? [HKQuantitySample] else {
                return
            }
            self.process(samples, type: quantityTypeIdentifier)
        }
        let query = HKAnchoredObjectQuery(type: HKObjectType.quantityType(forIdentifier: quantityTypeIdentifier)!, predicate: devicePredicate, anchor: nil, limit: HKObjectQueryNoLimit, resultsHandler: updateHandler)
        query.updateHandler = updateHandler
        healthStore.execute(query)
        activeQueries.append(query)
    }

    private func process(_ samples: [HKQuantitySample], type: HKQuantityTypeIdentifier) {
        for sample in samples {
            if type == .heartRate {
                DispatchQueue.main.async {
                    self.heartRateValue = sample.quantity.doubleValue(for: self.heartRateQuantity)
                }
            }
        }
    }

    func stopFetchingHeartRateData() {
        activeQueries.forEach { healthStore.stop($0) }
        activeQueries.removeAll()
        DispatchQueue.main.async {
            self.heartRateValue = 0.0
        }

    }
}

CombinedViewModel.swift

class CombinedViewModel: ObservableObject {

    @Published var motionValues: MotionValues = MotionValues()
    @Published var heartRateValue: Double = 0.0

    var motion = MotionManager()
    var health = HealthKitManager()

    var cancellables = Set<AnyCancellable>()

    init() {
        motion.$motionValues
            .combineLatest(health.$heartRateValue)
            .sink(receiveValue: { [weak self] combined  in
                self?.motionValues = combined.0
                self?.heartRateValue = combined.1
            })
            .store(in: &cancellables)
    }
}

ContentView.swift

struct ContentView: View {

    @State var isActive: Bool = false

    @ObservedObject var combined = CombinedViewModel()

    var body: some View {
        ScrollView {
            VStack(alignment: .leading) {
                Indicator(title: "X:", value: combined.motionValues.rotationX)
                Indicator(title: "Y:", value: combined.motionValues.rotationY)
                Indicator(title: "Z:", value: combined.motionValues.rotationZ)
                Divider()
                Indicator(title: "Pitch:", value: combined.motionValues.pitch)
                Indicator(title: "Roll:", value: combined.motionValues.roll)
                Indicator(title: "Yaw:", value: combined.motionValues.yaw)
                Divider()
                Indicator(title: "HR:", value: combined.heartRateValue)
            }
            .padding(.horizontal, 10)
            Button(action: {
                self.isActive.toggle()
                self.isActive ? self.start() : self.stop()
            }) {
                Text(isActive ? "Stop" : "Start")
            }
            .background(isActive ? Color.green : Color.blue)
            .cornerRadius(10)
            .padding(.horizontal, 5)
        }.onAppear {
            self.combined.health.autorizeHealthKit()
        }
    }

    private func start() {
        self.combined.motion.startMotionUpdates()
        self.combined.health.fetchHeartRateData(quantityTypeIdentifier: .heartRate)
    }

    private func stop() {
        self.combined.motion.stopMotionUpdates()
        self.combined.health.stopFetchingHeartRateData()
    }
}



struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct Indicator: View {

    var title: String
    var value: Double

    var body: some View {
        HStack {
            Text(title)
                .font(.footnote)
                .foregroundColor(.blue)
            Text("\(value)")
                .font(.footnote)
        }
    }
}
\$\endgroup\$
0

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.