iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 17
0
自我挑戰組

一天一蘋果,Bug 遠離我。系列 第 17

Day 17: 寫一個易讀的表單視圖!

利用 enum 配合我們的 UITableView / UICollectionView,寫出一個淺顯易懂的程式碼吧!

https://ithelp.ithome.com.tw/upload/images/20181101/20107701XW3RMi7YFn.png

前言:

在我們編寫 UITableView/UICollectionView 的時候,因為我們的 Cell 都是根據 IndexPath 來決定它的位置,必須告訴程式碼是哪個 section 哪個 row 我們才能夠對它進行操作,所以在程式碼我們常常會使用 if 或是 switch 語法來判斷。但是因為 IndexPath 這兩者的數值都是 Int,如果是個人開發的時候可能還好,但是一旦要跟別人共同開發的時候,其他開發人員可能不了解這些數字代表什麼或者是哪一個 Cell。所以下面的教學是我個人在開發這種表單視圖時會使用的方式。


#由設計圖決定你的架構

https://ithelp.ithome.com.tw/upload/images/20181101/20107701JCVHHTOj0T.png

首先我們來看一下我們的設計圖( 雖然已經是成品了,就假裝是設計圖 )

你可以看見我們有使用 UICollectionView 來開發出上面這個畫面,那這個時候的你是怎麼規劃開發這個畫面的呢?用 1分鐘的時間思考看看 ?
當然你用什麼方式都無所謂,只要能夠在正確的位置告訴 CollectionView 的 Datasource 是哪個 CollectionViewCell 就行了,不管你是用 IndexPathsection 或是 row 來區分他。但是這邊我想有概念的人可能會使用 section 方式來區分這兩種不同的 Cell,如此一來你不需要對 row 進行額外的計算,只需要知道是哪個 section 即可,再傳遞給 Cell 資料時也不需要額外的計算。

如此一來,你的 cellForItemAt 這段程式碼可能會類似於下面這段:

switch IndexPath.section {
  case 0:
    // 建置 UIColletionViewCell
    return TitleCell
  case 1:
    // 建置 UIColletionViewCell
    return ItemCell
  default:
    return UICollectionViewCell()
}

因為我們這個範例的 section 很明顯的就只有兩種,如果今天我們同時有很多個 section 並且從畫面上也很難分出是哪個 section 的 Cell,那麼我們該怎麼透過程式碼讓其他開發者了解這是哪個 section 呢?
這時,我們的 enum 這個型別就能派上用場了,以這個例子我們可以定義一個 enum 來分別這兩個 section ,並且將這個 enum 的型別定義為 Int

enum HomeSections: Int {
   case banner = 0, items
}

我們定義 HomeSections 型別為 Int 的原因是因為我們要透過其 rawValue 來實例化我們的 HomeSections ,我們可以透過 HomeSections(rawValue:) 傳入我們的 section 來產生我們 HomeSections 的 case,如果傳入的 rawValue 不包含在我們的 case 中,則會得到一個 nil
如此以來我們就能透過這個 HomeSections 初始化的結果來判斷是哪個 case ,如此一來我們也能清楚了解是哪個 section 下的操作:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let homeSection = HomeSections(rawValue: indexPath.section) else {
        return UICollectionViewCell()
    }
    switch homeSection {
    case .banner:
        let titleCell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeCellID.title, for: indexPath)
        return titleCell
    case .items:
        let itemCell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeCellID.item, for: indexPath)
        return itemCell
    }
}

如此一來我們只要透過程式碼就能夠了解他是在對哪個 section 進行操作,不用再去猜 section 的數字是代表哪個項目,透過 HomeSections 來賦予它一個 case,如此一來就能清楚地看出是哪個 section 下的操作。
當然如果也需要判斷你的 row 個別是哪個項目,你也可以建立一個 enum 將他們初始化成你的 case,透過 switch 這個 enum 下的結果,對每個 case 進行個別的操作,下方程式碼是我點選下方白色按鈕進行個別的操作:

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    // 判斷點擊區域是否為 .item
    if HomeSections(rawValue: indexPath.section) == .items {
        guard let selectedItem = HomeItems(rawValue: indexPath.row) else { return }
        switch selectedItem {
        case .bookShelf:
            self.performSegue(withIdentifier: HomeSegueID.bookshelfSegue, sender: nil)
        case .wishingBook:
            self.performSegue(withIdentifier: HomeSegueID.wishingBookSegue, sender: nil)
        case .scanner:
            self.performSegue(withIdentifier: HomeSegueID.scannerSegue, sender: nil)
        case .location:
            self.performSegue(withIdentifier: HomeSegueID.mapSegue, sender: nil)
        default:
            break
        }
    }
}

如此一來,不管我對哪個 case 進行操作,你應該都看得出來我是在對哪個按鈕項目進行操作吧,雖然都是執行 performSegue ,而我們的 Segue 的 identifier 也透過 HomeSegueID 這個 struct 包裝起來,並且使用 static 方式宣告,所以也不需要害怕打錯程式碼囉~


後記:

雖然大家再開發的過程大部分的時間都還是有時程或是其他的因素存在,所以導致可能 IndexPath 還是大部分都還是使用數字來判斷,但是如果我們能夠使用 enum 的方式來編寫程式碼,不僅清楚明白 IndexPath 代表著什麼,同時有多個開發者也能有個易讀的程式碼架構能夠維護。


上一篇
Day 16: 製作一個 QR Code 掃描器!
下一篇
Day 18: 在 App 中開啟一個 Safari 瀏覽器吧!
系列文
一天一蘋果,Bug 遠離我。30

尚未有邦友留言

立即登入留言