你是否也曾因為一在發生的 UI 錯誤煩惱? 當 QA 又
回報某個頁面錯誤,[Bug] 精選頁面 Load more 未顯示 Loading indicator,為什麼要說又呢?
如果你也有這樣的症頭,請繼續看下去。
雖然我們依循 Model-View-Controller(MVC) 的架構開發,但對於複雜的 UI 狀態改變還是會出錯。為什麼?
回顧原先我們是如何管理複雜的狀態:
Class KKFeaturedViewController: UIViewController {
var cards: [Card]
var isLoading: Bool
var isReloading: Bool
var hasMore: Bool
// ...
}
沒錯,我們最初建立很多個 flag 來表示狀態,乍看之下沒什麼問題。讓我們回到 QA 回報的問題 [Bug] 精選頁面 Load more 未顯示 Loading indicator ,可以從上面的變數中推測當 Load more 時,會是什麼狀態?
- cards.count > 0
- isLoading == true
- isReloading == false
- hasMore == false || true
由下列程式碼中很清楚的光是 cards.count
跟 hasMore
就定義了四種狀態。更不用說在此架構下若要滴水不漏,你需要處理 2 的 4 次方,也就是 16 種狀態。
Class KKFeaturedViewController: UIViewController {
// ...
func fetchCardAPI() {
// ...
isLoading = true
if hasMore {
if cards.count == 0 {
// First load without content (reload)
}
else {
// Loading more with content (load more)
}
}
else {
if cards.count == 0 {
// Empty content
}
else {
// Content complete
}
}
}
}
其實有些狀態組合並不存在。
若 16 個狀態組合中出現了不該出現的狀態,Bug 就產生了。而且這樣的程式碼不容易分辨哪些狀態有處理過,哪些沒有,像是複雜的電路板到處接來接去,出錯時不容易找到問題出在哪,當然也就不容易修正。
我想有限狀態機 可以幫我解決這個問題。有限狀態機的概念是 在有線個數的狀態裡,狀態之間轉移的數學模型
。 主要的元素有 狀態
, 動作(事件)
來延伸出 狀態A
+ 事件
=> 狀態B
。最常見的應用是交通燈(紅綠燈)
,也就是 綠燈時
+ 60秒
=> 黃燈
以此類推。
回過頭來整理真正會出現的狀態,原本 16 種狀態只有成 8 種會出現,一半以上的狀態都可以忽略 (難怪這麼多 Bug )
參考How to fix a bad user interface一文中並實作其 UI Stack 的概念。
狀態實作
在我們情境下需要考慮到較多的狀態,所以就從原本的 5 種延伸成下列 8 種(請你視使用情境而自行調整成需要的狀態)。
將 ViewController 的權責從 處理狀態轉換 + 過濾不必要的狀態 + 各個子UI管理
被簡化成 根據狀態顯示UI
,並且集中管理。
enum UIStack {
// Blank State
case initial // 初始狀態尚未開始載入資料
// Loading State
case initialLoading // 初始載入,可與 Reload 共用
case partialWithLoading // 載入部分資料,但又觸發載入下一頁
// Partial State
case partial //載入部分資料
case partialWithError //載入部分資料,但載入新資料過程有誤
// Error State
case error // 錯誤
// Ideal State
case perfect // 資料完整載入
case empty // 打過 API 但是沒有資料 (empty inbox 的概念)
}
Class ListViewController: UIViewController {
internal var state: UIStack = .initial {
didSet {
// * switch cases ... * //
}
}
}
總結
把設定狀態
跟 顯示UI
的邏輯分離後會讓事情簡單很多,防止自己粗心大意。
開發加速
開發過程中可隨時模擬狀態,就連部分載入但有錯誤的情境都可輕易重現。
舉例來說,想要重現 contentEmpty
這個狀態,在以前我可能要去修改 API Clint 去偽裝這隻 API 回應給我 0 個資料,而現在只要直接改 state
即可。
容易除錯
當 UI 出錯時就可以分成兩種錯誤,一種是狀態錯了,另一種是對應的 UI 錯了。
程式碼集中
跟狀態相關的程式碼會集中在一個地方,統一實作。
防呆
狀態一但被定義,Swift 的 Switch case 會強迫你要實作所有 cases,不讓我們有偷懶的機會。
No Comments