在 SwiftUI 中组合异步 return 值
Combine asynchronous return values in SwiftUI
我有 2 个来自 2 个不同 类 的异步 return 值,一个来自 HealthKit,另一个来自 MotionManager。我的目标是组合这些值并将它们输出到 swiftui View 中,它每秒刷新一次。我知道我必须在这里查看 combine 框架,但我不知道从哪里开始。我找不到很多描述 Swiftui + Combine 的教程。我知道我必须看看 .combineLatest 但我是否必须编写自己的发布者和订阅者,或者我可以使用我这里的 @Published 属性 包装器(@Published var motionData = MotionData()和@Published var heartRateValue : 双 = 0.0) ?
我的 MotionManager Class:
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: ObservableObject {
@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 Class:
class HealthKitManager: ObservableObject {
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([=12=]) }
activeQueries.removeAll()
DispatchQueue.main.async {
self.heartRateValue = 0.0
}
}
}
我开始创建一个 combinedViewModel,但我被困在这里,不知道这是否可行:
class CombinedViewModel: ObservableObject {
@Published var motionManager: MotionManager = MotionManager()
@Published var healthManager: HealthKitManager = HealthKitManager()
var anyCancellable: AnyCancellable?
init() {
anyCancellable = Publishers
.CombineLatest(motionManager.$motionValues,healthManager.$heartRateValue)
.sink(receiveValue: {
// Do something
}
})
}
}
我需要关注哪里?我是否需要完全学习组合框架来编写我自己的发布者和订阅者,或者@Published 是否有可用的东西可以完成这项工作?或者我是否需要使用我的 CombinedViewModel 采取另一种方法?
已添加 contentView 以供参考:
struct ContentView: View {
@State var isActive: Bool = false
private var motion = MotionManager()
private var health = HealthKitManager()
@ObservedObject var combinedViewModel = CombinedViewModel(managerOne: motion, managerTwo: health)
private var motionValues: MotionValues {
return combinedViewModel.combinedValues.0
}
private var heartRateValue: Double {
return combinedViewModel.combinedValues.1
}
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Indicator(title: "X:", value: motionValues.rotationX)
Indicator(title: "Y:", value: motionValues.rotationY)
Indicator(title: "Z:", value: motionValues.rotationZ)
Divider()
Indicator(title: "Pitch:", value: motionValues.pitch)
Indicator(title: "Roll:", value: motionValues.roll)
Indicator(title: "Yaw:", value: motionValues.yaw)
Divider()
Indicator(title: "HR:", value: 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.health.autorizeHealthKit()
}
}
private func start() {
self.motion.startMotionUpdates()
self.health.fetchHeartRateData(quantityTypeIdentifier: .heartRate)
}
private func stop() {
self.motion.stopMotionUpdates()
self.health.stopFetchingHeartRateData()
}
}
您可以在 CombinedViewModel
中创建一个新的发布者(我建议使用 AnyPublisher
),它结合了两者的输出。这是带有 CombinedViewModel
:
的代码的简化版本
class ManagerOne {
@Published var someValue = "Some Value"
}
class ManagerTwo {
@Published var otherValue = "Other Value"
}
class CombinedViewModel {
var combinedPublisher: AnyPublisher<(String, String), Never>
init(managerOne: ManagerOne, managerTwo: ManagerTwo) {
combinedPublisher = managerOne.$someValue
.combineLatest(managerTwo.$otherValue)
.eraseToAnyPublisher()
}
}
如果您需要 CombinedViewModel
成为观察对象,您可以将代码调整为更像这样:
class CombinedViewModel: ObservableObject {
@Published var combinedValue: (String, String) = ("", "")
var cancellables = Set<AnyCancellable>()
init(managerOne: ManagerOne, managerTwo: ManagerTwo) {
managerOne.$someValue
.combineLatest(managerTwo.$otherValue)
.sink(receiveValue: { [weak self] combined in
self?.combinedValue = combined
})
.store(in: &cancellables)
}
}
关于此的附注:
@Published var motionManager: MotionManager = MotionManager()
@Published var healthManager: HealthKitManager = HealthKitManager()
由于这两个管理器都是 类,因此 $motionManager
和 $healthManager
只会在您分配 MotionManager
或 HealthKitManager
的新实例时发出值给他们。不是当任何一位经理的 属性 发生变化时。
我有 2 个来自 2 个不同 类 的异步 return 值,一个来自 HealthKit,另一个来自 MotionManager。我的目标是组合这些值并将它们输出到 swiftui View 中,它每秒刷新一次。我知道我必须在这里查看 combine 框架,但我不知道从哪里开始。我找不到很多描述 Swiftui + Combine 的教程。我知道我必须看看 .combineLatest 但我是否必须编写自己的发布者和订阅者,或者我可以使用我这里的 @Published 属性 包装器(@Published var motionData = MotionData()和@Published var heartRateValue : 双 = 0.0) ?
我的 MotionManager Class:
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: ObservableObject {
@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 Class:
class HealthKitManager: ObservableObject {
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([=12=]) }
activeQueries.removeAll()
DispatchQueue.main.async {
self.heartRateValue = 0.0
}
}
}
我开始创建一个 combinedViewModel,但我被困在这里,不知道这是否可行:
class CombinedViewModel: ObservableObject {
@Published var motionManager: MotionManager = MotionManager()
@Published var healthManager: HealthKitManager = HealthKitManager()
var anyCancellable: AnyCancellable?
init() {
anyCancellable = Publishers
.CombineLatest(motionManager.$motionValues,healthManager.$heartRateValue)
.sink(receiveValue: {
// Do something
}
})
}
}
我需要关注哪里?我是否需要完全学习组合框架来编写我自己的发布者和订阅者,或者@Published 是否有可用的东西可以完成这项工作?或者我是否需要使用我的 CombinedViewModel 采取另一种方法?
已添加 contentView 以供参考:
struct ContentView: View {
@State var isActive: Bool = false
private var motion = MotionManager()
private var health = HealthKitManager()
@ObservedObject var combinedViewModel = CombinedViewModel(managerOne: motion, managerTwo: health)
private var motionValues: MotionValues {
return combinedViewModel.combinedValues.0
}
private var heartRateValue: Double {
return combinedViewModel.combinedValues.1
}
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Indicator(title: "X:", value: motionValues.rotationX)
Indicator(title: "Y:", value: motionValues.rotationY)
Indicator(title: "Z:", value: motionValues.rotationZ)
Divider()
Indicator(title: "Pitch:", value: motionValues.pitch)
Indicator(title: "Roll:", value: motionValues.roll)
Indicator(title: "Yaw:", value: motionValues.yaw)
Divider()
Indicator(title: "HR:", value: 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.health.autorizeHealthKit()
}
}
private func start() {
self.motion.startMotionUpdates()
self.health.fetchHeartRateData(quantityTypeIdentifier: .heartRate)
}
private func stop() {
self.motion.stopMotionUpdates()
self.health.stopFetchingHeartRateData()
}
}
您可以在 CombinedViewModel
中创建一个新的发布者(我建议使用 AnyPublisher
),它结合了两者的输出。这是带有 CombinedViewModel
:
class ManagerOne {
@Published var someValue = "Some Value"
}
class ManagerTwo {
@Published var otherValue = "Other Value"
}
class CombinedViewModel {
var combinedPublisher: AnyPublisher<(String, String), Never>
init(managerOne: ManagerOne, managerTwo: ManagerTwo) {
combinedPublisher = managerOne.$someValue
.combineLatest(managerTwo.$otherValue)
.eraseToAnyPublisher()
}
}
如果您需要 CombinedViewModel
成为观察对象,您可以将代码调整为更像这样:
class CombinedViewModel: ObservableObject {
@Published var combinedValue: (String, String) = ("", "")
var cancellables = Set<AnyCancellable>()
init(managerOne: ManagerOne, managerTwo: ManagerTwo) {
managerOne.$someValue
.combineLatest(managerTwo.$otherValue)
.sink(receiveValue: { [weak self] combined in
self?.combinedValue = combined
})
.store(in: &cancellables)
}
}
关于此的附注:
@Published var motionManager: MotionManager = MotionManager()
@Published var healthManager: HealthKitManager = HealthKitManager()
由于这两个管理器都是 类,因此 $motionManager
和 $healthManager
只会在您分配 MotionManager
或 HealthKitManager
的新实例时发出值给他们。不是当任何一位经理的 属性 发生变化时。