灵感来自 SwiftUI 的状态观察,但无需 SwiftUI
就像 ObservableObject
一样,但没有任何 SwiftUI 或 Combine 的东西
class MyView: UIView {
/// Conform your model classes to `ObservableState`
class ViewModel: ObservableState {
/// Use `ObservedValue` for your model’s properties
@ObservedValue var count: Int = 0
}
class StepperView: UIView {
/// Store passed through bindings with `ValueProxy`
@ValueProxy var count: Int
lazy var minusButton = UIButton(frame: .zero, primaryAction: UIAction { [unowned self] _ in
count -= 1
})
lazy var plusButton = UIButton(frame: .zero, primaryAction: UIAction { [unowned self] _ in
count += 1
})
init(count: ValueProxy<Int>) {
self._count = count
super.init(frame: .zero)
self.addSubview(minusButton)
self.addSubview(plusButton)
}
}
@ObservedState var viewModel = ViewModel()
var observers: [StateValueObserver] = []
lazy var countLabel = UILabel()
// Pass value proxy to ViewModel’s count property
lazy var stepper = StepperView(count: $viewModel.count)
func setupView() {
addSubview(stepper)
// Use an update handler to set the label’s text when count updates
$viewModel.count.didUpdate { [weak self] count in
self?.countLabel.text = "\(count)"
}.add(to: &observers)
}
}
(基本的计数器示例代码,演示了更新 ValueProxy
和 didUpdate
逻辑)
要将此依赖项添加到您的 Xcode 项目,请选择“File”->“Add Package”,然后输入此存储库的 URL:https://github.com/PimCoumans/DidUpdate
SwiftUI 很棒,但目前我更喜欢使用纯旧的 UIKit 来处理我应用程序中更复杂的部分。我确实喜欢 SwiftUI 让你可以定义状态,并在任何更改发生时自动更新所有视图的方式。我想要那个,但不想导入 SwiftUI 或 Combine 并使用一堆 publishers,或者学习一个全新的响应式库带来的额外开销。
所以我逆向过度工程了我喜欢的部分,并引入了向你的绑定(DidUpdate 中的 ValueProxy
)添加更新处理程序的能力。
现在你也可以为你的 UIKit 视图拥有一个微小的响应式架构了!
两个主要特性是
Equatable
,您将知道它的值何时真正被更改。didSet { }
。还可以方便地使用 KeyPath 下标创建到嵌套属性的绑定(例如 $viewModel.someFrame.size.width
)。要启用这种魔法,请确保您的模型对象符合 ObservableState
,并在您的视图(控制器)中使用 @ObservedState
属性包装器来持有它。对于您模型的所有属性,当您希望这些属性可观察时,请使用 @ObservedValue
。再次查看上面的示例,了解它们如何组合在一起。
在所有值属性上,您都会获得一堆 didUpdate
方法,允许您提供在属性更新时执行的更新处理程序。
let observer = $viewModel.username.didUpdate { username in
print("Username updated to: \(username)")
}
或者当您在其他视图中设置了 @ValueProxy
时
let observer = $username.didUpdate { username in
print("Username updated to: \(username)")
}
理想情况下,您会将那些返回的观察者存储在一个数组中,就像 [AnyCancellable]
一样
var observers: [StateValueObserver] = []
func addObservers() {
$username.didUpate { newValue in
// ...
}.add(to: &observers)
}
除了 didUpdate
之外,还有 didChange
指示该值已实际更改(意味着在符合 Equatable
时不被认为是相等的)
let observer = $viewModel.username.didChange { username in
print("Username has changed to: \(username)")
}
以及 didChange(comparing:)
来比较给定键路径上的值
// Update handler only called when username.isEmpty changes
let observer = $viewModel.username.didChange(comparing: \.isEmpty) { username in
if !username.isEmpty {
print("Username no longer empty")
} else {
print("Username empty again")
}
}
要将这些值的双向绑定传递出去,您可以通过访问您对象的属性包装器的投影值(使用 $
)来创建一个 ValueProxy
class SubView: UIView {
@ValueProxy var username: String
init(username: ValueProxy<String>) {
_username = username
}
}
// in your main view, access the projected value using the `$` prefix
let someSubView = SubView(username: $viewModel.username)
在此示例中,更改 SubView
中的 username 属性会自动更新您 viewModel 中的属性。读取 SubView
中的 username
属性将为您提供实际的最新值,即使是从其他地方更改的(就像您从 @Binding
中期望的那样)。
差不多就是这样!如果您有任何问题,请 告诉我。