DidUpdate

灵感来自 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)
    }
}

(基本的计数器示例代码,演示了更新 ValueProxydidUpdate 逻辑)

📦 安装

要将此依赖项添加到您的 Xcode 项目,请选择“File”->“Add Package”,然后输入此存储库的 URL:https://github.com/PimCoumans/DidUpdate

🤷 但是,为什么?

SwiftUI 很棒,但目前我更喜欢使用纯旧的 UIKit 来处理我应用程序中更复杂的部分。我确实喜欢 SwiftUI 让你可以定义状态,并在任何更改发生时自动更新所有视图的方式。我想要那个,但不想导入 SwiftUI 或 Combine 并使用一堆 publishers,或者学习一个全新的响应式库带来的额外开销。

所以我逆向过度工程了我喜欢的部分,并引入了向你的绑定(DidUpdate 中的 ValueProxy)添加更新处理程序的能力。

现在你也可以为你的 UIKit 视图拥有一个微小的响应式架构了!

↔️它到底做了什么?

两个主要特性是

✨ 我该如何做?

要启用这种魔法,请确保您的模型对象符合 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")
    }
}

双向绑定 (value proxies)

要将这些值的双向绑定传递出去,您可以通过访问您对象的属性包装器的投影值(使用 $)来创建一个 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 中期望的那样)。

❓就这些?

差不多就是这样!如果您有任何问题,请 告诉我