使用 Combine 和 SwiftUI 显示变化值的最简洁方法是什么?
What is the most concise way to display a changing value with Combine and SwiftUI?
我正在努力思考 SwiftUI 和 Combine。我想使 UI 中的一些文本保持最新并具有一个值。例如,在这种情况下,它是设备的电池电量。
这是我的代码。首先,这似乎是实现我想做的事情的相当多的代码,所以我想知道我是否可以不用其中的一些代码。此外,这段代码曾经 运行 整个夏天,但现在它崩溃了,可能是由于 SwiftUI 和 Combine 的变化。
如何修复此问题以与当前版本的 SwiftUI 和 Combine 一起使用?而且,是否可以减少此处的代码量来做同样的事情?
import SwiftUI
import Combine
class ViewModel: ObservableObject {
var willChange = PassthroughSubject<Void, Never>()
var batteryLevelPublisher = UIDevice.current
.publisher(for: \.batteryLevel)
.receive(on: RunLoop.main)
lazy var batteryLevelSubscriber = Subscribers.Assign(object: self,
keyPath: \.batteryLevel)
var batteryLevel: Float = UIDevice.current.batteryLevel {
didSet {
willChange.send()
}
}
init() {
batteryLevelPublisher.subscribe(batteryLevelSubscriber)
}
}
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
Text("\(Int(round(viewModel.batteryLevel * 100)))%")
}
}
要在 iPadOS Swift playground 中粘贴的最小工作示例。
基本上,它是与 SwiftUI 和 Combine.
的最新更改一致的代码。
将@Published 属性 包装器用于您想要在视图中观察的任何属性(文档:默认情况下,ObservableObject 合成一个 objectWillChange 发布者,该发布者在其任何之前发出更改的值@Published 属性更改。)。这避免了使用自定义设置器和 objectWillChange.
可取消是Publishers.Assign的输出,可用于手动取消订阅,最佳实践是将其存储在“CancellableBag”中,从而取消订阅取消初始化。这种做法受到其他响应式框架的启发,例如 RxSwift 和 ReactiveUI.
我发现在不打开电池电量通知的情况下,KVO 电池电量发布者只会发出一次 -1.0。
// iPadOS playground
import SwiftUI
import Combine
import PlaygroundSupport
class BatteryModel : ObservableObject {
@Published var level = UIDevice.current.batteryLevel
private var cancellableSet: Set<AnyCancellable> = []
init () {
UIDevice.current.isBatteryMonitoringEnabled = true
assignLevelPublisher()
}
private func assignLevelPublisher() {
_ = UIDevice.current
.publisher(for: \.batteryLevel)
.assign(to: \.level, on: self)
.store(in: &self.cancellableSet)
}
}
struct ContentView: View {
@ObservedObject var batteryModel = BatteryModel()
var body: some View {
Text("\(Int(round(batteryModel.level * 100)))%")
}
}
let host = UIHostingController(rootView: ContentView())
PlaygroundPage.current.liveView = host
这是一个通用的解决方案,适用于任何支持 KVO 的对象:
class KeyPathObserver<T: NSObject, V>: ObservableObject {
@Published var value: V
private var cancel = Set<AnyCancellable>()
init(_ keyPath: KeyPath<T, V>, on object: T) {
value = object[keyPath: keyPath]
object.publisher(for: keyPath)
.assign(to: \.value, on: self)
.store(in: &cancel)
}
}
因此,要在您的视图中监控电池电量(例如),您需要像这样添加一个 @ObservedObject
@ObservedObject batteryLevel = KeyPathObserver(\.batteryLevel, on: UIDevice.current)
然后您可以直接或通过 @ObservedObject
的 value
属性
访问该值
batteryLevel.value
我正在努力思考 SwiftUI 和 Combine。我想使 UI 中的一些文本保持最新并具有一个值。例如,在这种情况下,它是设备的电池电量。
这是我的代码。首先,这似乎是实现我想做的事情的相当多的代码,所以我想知道我是否可以不用其中的一些代码。此外,这段代码曾经 运行 整个夏天,但现在它崩溃了,可能是由于 SwiftUI 和 Combine 的变化。
如何修复此问题以与当前版本的 SwiftUI 和 Combine 一起使用?而且,是否可以减少此处的代码量来做同样的事情?
import SwiftUI
import Combine
class ViewModel: ObservableObject {
var willChange = PassthroughSubject<Void, Never>()
var batteryLevelPublisher = UIDevice.current
.publisher(for: \.batteryLevel)
.receive(on: RunLoop.main)
lazy var batteryLevelSubscriber = Subscribers.Assign(object: self,
keyPath: \.batteryLevel)
var batteryLevel: Float = UIDevice.current.batteryLevel {
didSet {
willChange.send()
}
}
init() {
batteryLevelPublisher.subscribe(batteryLevelSubscriber)
}
}
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
Text("\(Int(round(viewModel.batteryLevel * 100)))%")
}
}
要在 iPadOS Swift playground 中粘贴的最小工作示例。
基本上,它是与 SwiftUI 和 Combine.
的最新更改一致的代码。将@Published 属性 包装器用于您想要在视图中观察的任何属性(文档:默认情况下,ObservableObject 合成一个 objectWillChange 发布者,该发布者在其任何之前发出更改的值@Published 属性更改。)。这避免了使用自定义设置器和 objectWillChange.
可取消是Publishers.Assign的输出,可用于手动取消订阅,最佳实践是将其存储在“CancellableBag”中,从而取消订阅取消初始化。这种做法受到其他响应式框架的启发,例如 RxSwift 和 ReactiveUI.
我发现在不打开电池电量通知的情况下,KVO 电池电量发布者只会发出一次 -1.0。
// iPadOS playground
import SwiftUI
import Combine
import PlaygroundSupport
class BatteryModel : ObservableObject {
@Published var level = UIDevice.current.batteryLevel
private var cancellableSet: Set<AnyCancellable> = []
init () {
UIDevice.current.isBatteryMonitoringEnabled = true
assignLevelPublisher()
}
private func assignLevelPublisher() {
_ = UIDevice.current
.publisher(for: \.batteryLevel)
.assign(to: \.level, on: self)
.store(in: &self.cancellableSet)
}
}
struct ContentView: View {
@ObservedObject var batteryModel = BatteryModel()
var body: some View {
Text("\(Int(round(batteryModel.level * 100)))%")
}
}
let host = UIHostingController(rootView: ContentView())
PlaygroundPage.current.liveView = host
这是一个通用的解决方案,适用于任何支持 KVO 的对象:
class KeyPathObserver<T: NSObject, V>: ObservableObject {
@Published var value: V
private var cancel = Set<AnyCancellable>()
init(_ keyPath: KeyPath<T, V>, on object: T) {
value = object[keyPath: keyPath]
object.publisher(for: keyPath)
.assign(to: \.value, on: self)
.store(in: &cancel)
}
}
因此,要在您的视图中监控电池电量(例如),您需要像这样添加一个 @ObservedObject
@ObservedObject batteryLevel = KeyPathObserver(\.batteryLevel, on: UIDevice.current)
然后您可以直接或通过 @ObservedObject
的 value
属性
batteryLevel.value