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)
}
新的設計把這個組合類控件的兩個功能拆分開了,操作控件之后內部只會拋出事件,而不會去處理事件,比如點擊了?按鈕,那這個類只負責告訴外界有這個事件發生,具體怎么解釋這個事件是外界的職責。外界解釋好這個事件之后(更新數據),再由外界根據數據去更新控件的顯示內容。
這樣做有幾個好處:
- 不需要復雜的初始化方法提供用于邏輯控制的變量(像例子里的取值區間和操作梯度)
- 不對邏輯進行處理的控件使得其本身更簡潔易用、復用性更高。
- 符合 “UI拋出事件-數據驅動UI”的編程思想
5. 總結
設計控件時一定要避免事件直接驅動UI,而是將控件的職責分成兩部分:向外界拋出事件和向外界提供更新UI的方法,這兩者之間的邏輯應該交由外界去處理。