設(shè)計組合型UI控件中的編程思想:UI拋出事件-數(shù)據(jù)驅(qū)動UI

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

CocoaTouch框架里提供的UI控件分為兩類:一類是是用于展示的控件UIView及其子類,另外一類是用于交互的控件UIControl及其子類。我們在實際開發(fā)中經(jīng)常會遇到將兩類控件組合在一起封裝成一個自定義的控件,這種控件可以交互,并對交互的結(jié)果進行展示,例如購物車里的數(shù)量編輯條控件,點擊?和?按鈕,中間的數(shù)量label會相應的更新;再例如文檔里的頁切換控件,點擊←和→按鈕,中間的頁碼label也會相應的更新。接下來以我在實際開發(fā)中遇到的一個需求來分析一下這類組合型控件在設(shè)計時應該遵循的編程思想。


count_bar.jpg

2. 案例導入

價格設(shè)置條。產(chǎn)品需求是價格區(qū)間在0.1~9.9,每次加和減的梯度是0.1。


price_bar.png

3. 不好的設(shè)計

先說一個不好的設(shè)計,關(guān)鍵部分代碼如下

  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 ) {
   ...
  }

這個設(shè)計的思路是 價格區(qū)間和操作梯度由初始化時自定義提供,內(nèi)部維護一個price變量,當點擊加或者減的時候先判斷修改后的price是否在區(qū)間內(nèi),滿足條件就去修改,然后去修改顯示標簽,并通過一個閉包告知使用者數(shù)據(jù)發(fā)生變化。

這個設(shè)計不好的地方在于控件內(nèi)部對交互的結(jié)果作出了處理。控件只應該負責拋出事件提供跟新UI的方法,而不是通過復雜的初始化方法提供變量用來自己解釋交互的結(jié)果,這樣定義的控件不符合編程思想,使用起來也是局限性非常大。

4. 合理的設(shè)計

自定義組合控件

 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)
 }

新的設(shè)計把這個組合類控件的兩個功能拆分開了,操作控件之后內(nèi)部只會拋出事件,而不會去處理事件,比如點擊了?按鈕,那這個類只負責告訴外界有這個事件發(fā)生,具體怎么解釋這個事件是外界的職責。外界解釋好這個事件之后(更新數(shù)據(jù)),再由外界根據(jù)數(shù)據(jù)去更新控件的顯示內(nèi)容。
這樣做有幾個好處:

  1. 不需要復雜的初始化方法提供用于邏輯控制的變量(像例子里的取值區(qū)間和操作梯度)
  2. 不對邏輯進行處理的控件使得其本身更簡潔易用、復用性更高。
  3. 符合 “UI拋出事件-數(shù)據(jù)驅(qū)動UI”的編程思想

5. 總結(jié)

設(shè)計控件時一定要避免事件直接驅(qū)動UI,而是將控件的職責分成兩部分:向外界拋出事件向外界提供更新UI的方法,這兩者之間的邏輯應該交由外界去處理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容