如何为UIStepper实现观察者设计模式以同步更新多个自定义子视图
实现方案
核心逻辑是通过观察者模式,让VC持有可被观测的计数器属性,数值变更时自动通知所有订阅方,子视图接收到新值后自行刷新UI即可。以下是两种常用的落地实现:
方案1:使用Combine框架(推荐,iOS 13+支持)
类型安全,无需手动管理观察者释放,是目前iOS生态下观察者模式的首选实现。
步骤1:VC层配置可观测计数器与UIStepper绑定
import UIKit import Combine class MainViewController: UIViewController { // @Published 注解会自动在值变化时对外发布变更事件 @Published var counter: Int = 0 // 存储订阅关系,避免订阅被提前释放 private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() setupStepper() setupChildViews() } private func setupStepper() { let stepper = UIStepper() stepper.minimumValue = 0 stepper.maximumValue = 100 stepper.stepValue = 1 stepper.addTarget(self, action: #selector(stepperDidChange(_:)), for: .valueChanged) // 此处省略stepper的布局、添加到父视图代码 view.addSubview(stepper) } @objc private func stepperDidChange(_ sender: UIStepper) { // 赋值操作会自动触发@Published的事件发布 counter = Int(sender.value) } private func setupChildViews() { let firstView = FirstCustomView() let secondView = SecondCustomView() // 此处省略两个子视图的布局、添加到父视图代码 view.addSubview(firstView) view.addSubview(secondView) // 给第一个子视图绑定计数器更新事件 $counter .receive(on: DispatchQueue.main) // 确保UI更新在主线程执行 .sink { [weak firstView] newValue in firstView?.refresh(with: newValue) } .store(in: &cancellables) // 给第二个子视图绑定计数器更新事件 $counter .receive(on: DispatchQueue.main) .sink { [weak secondView] newValue in secondView?.refresh(with: newValue) } .store(in: &cancellables) } }
步骤2:实现两个自定义子视图
// 第一个子视图:展示「My value is X」 class FirstCustomView: UIView { private let displayLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) setupUI() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupUI() { addSubview(displayLabel) // 此处省略displayLabel的样式配置、布局代码 } // 对外暴露的刷新方法 func refresh(with value: Int) { displayLabel.text = "My value is \(value)" } } // 第二个子视图:展示「Counter: 'X'」 class SecondCustomView: UIView { private let displayLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) setupUI() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupUI() { addSubview(displayLabel) // 此处省略displayLabel的样式配置、布局代码 } func refresh(with value: Int) { displayLabel.text = "Counter: '\(value)'" } }
方案2:使用NotificationCenter(兼容iOS 12及更低版本)
如果需要兼容旧版本系统,可以用系统通知中心实现广播能力。
步骤1:定义通知名称
extension Notification.Name { static let counterDidUpdate = Notification.Name("counterDidUpdate") }
步骤2:VC数值变化时发送通知
@objc private func stepperDidChange(_ sender: UIStepper) { let newValue = Int(sender.value) NotificationCenter.default.post( name: .counterDidUpdate, object: nil, userInfo: ["value": newValue] ) }
步骤3:子视图内部监听通知
以第一个子视图为例:
class FirstCustomView: UIView { private let displayLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) setupUI() // 注册通知监听 NotificationCenter.default.addObserver( self, selector: #selector(handleCounterNoti(_:)), name: .counterDidUpdate, object: nil ) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc private func handleCounterNoti(_ noti: Notification) { guard let value = noti.userInfo?["value"] as? Int else { return } displayLabel.text = "My value is \(value)" } // 销毁时移除观察者避免野指针 deinit { NotificationCenter.default.removeObserver(self) } // 省略其他UI相关代码 }
第二个子视图的监听逻辑和上面一致,只需要修改refresh的文案即可。
内容的提问来源于stack exchange,提问作者akku
相关产品推荐
相关产品推荐

