设计组合型UI控件中的编程思想:UI抛出事件-数据驱动UI

1. 展示型控件、交互性控件、组合型控件

CocoaTouch框架里提供的UI控件分为两类:一类是是用于展示的控件UIView及其子类,另外一类是用于交互的控件UIControl及其子类。我们在实际开发中经常会遇到将两类控件组合在一起封装成一个自定义的控件,这种控件可以交互,并对交互的结果进行展示,例如购物车里的数量编辑条控件,点击➕和➖按钮,中间的数量label会相应的更新;再例如文档里的页切换控件,点击←和→按钮,中间的页码label也会相应的更新。接下来以我在实际开发中遇到的一个需求来分析一下这类组合型控件在设计时应该遵循的编程思想。


count_bar.jpg

2. 案例导入

价格设置条。产品需求是价格区间在0.1~9.9,每次加和减的梯度是0.1。


price_bar.png

3. 不好的设计

先说一个不好的设计,关键部分代码如下

  var priceChangeHandle: ((_ price: CGFloat) -> Void)?    
  var price: CGFloat {
        didSet {
            priceLabel.text = String(format: "%.1f", price)
            priceChangeHandle?(price)
        }
    }
  init(frame: CGRect, price: CGFloat, max: CGFloat = 99.9, min: CGFloat = 0.1, degree: CGFloat = 0.1 ) {
   ...
  }

这个设计的思路是 价格区间和操作梯度由初始化时自定义提供,内部维护一个price变量,当点击加或者减的时候先判断修改后的price是否在区间内,满足条件就去修改,然后去修改显示标签,并通过一个闭包告知使用者数据发生变化。

这个设计不好的地方在于控件内部对交互的结果作出了处理。控件只应该负责抛出事件提供跟新UI的方法,而不是通过复杂的初始化方法提供变量用来自己解释交互的结果,这样定义的控件不符合编程思想,使用起来也是局限性非常大。

4. 合理的设计

自定义组合控件

 enum EditAction {
        case increase
        case decrease
    }
 override init(frame: CGRect) {
   super.init(frame: frame)
   decreaseItem.editHandle = { [weak self] in
            guard let self = self else {
                return
            }
            if let handle = self.editActionHandle {
                self.amountLabel.text = handle(.decrease)
            }
    }
   increaseItem.editHandle = { [weak self] in
            guard let self = self else {
                return
            }
            if let handle = self.editActionHandle {
                self.amountLabel.text = handle(.increase)
            }
    }
}
func regist(editActionHandle: @escaping ((_ action: EditAction) -> String)) {
        self.editActionHandle = editActionHandle
 }

使用控件

priceEditBar.regist { [weak self] action -> String in
            guard let self = self else { return "0.1" }
            switch action {
            case .increase:
                if abs(self.price-0.9) > 0.01 {
                    self.price += 0.1
                }
            case .decrease:
                if abs(self.price-0.1) > 0.01 {
                    self.price -= 0.1
                }
            }
            return String(format: "%.1f", self.price)
 }

新的设计把这个组合类控件的两个功能拆分开了,操作控件之后内部只会抛出事件,而不会去处理事件,比如点击了➕按钮,那这个类只负责告诉外界有这个事件发生,具体怎么解释这个事件是外界的职责。外界解释好这个事件之后(更新数据),再由外界根据数据去更新控件的显示内容。
这样做有几个好处:

  1. 不需要复杂的初始化方法提供用于逻辑控制的变量(像例子里的取值区间和操作梯度)
  2. 不对逻辑进行处理的控件使得其本身更简洁易用、复用性更高。
  3. 符合 “UI抛出事件-数据驱动UI”的编程思想

5. 总结

设计控件时一定要避免事件直接驱动UI,而是将控件的职责分成两部分:向外界抛出事件向外界提供更新UI的方法,这两者之间的逻辑应该交由外界去处理。

你可能感兴趣的:(设计组合型UI控件中的编程思想:UI抛出事件-数据驱动UI)