iOS 軟體開發

實作 UI Stack – 簡化邏輯讓 UI 不再出錯

你是否也曾因為一在發生的 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 時,會是什麼狀態?

  1. cards.count > 0
  2. isLoading == true
  3. isReloading == false
  4. hasMore == false || true

由下列程式碼中很清楚的光是 cards.counthasMore 就定義了四種狀態。更不用說在此架構下若要滴水不漏,你需要處理 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 的概念。

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,不讓我們有偷懶的機會。

Reference

You Might Also Like

No Comments

    發表迴響