EmptyPage(空白頁元件)原理與使用

歸故里發表於2018-11-17

app 顯示列表內容時, 在某一時刻可能資料為空(等待網路請求/網路請求失敗)等, 新增一個空白指示頁將有效緩解使用者可能造成的焦慮或混亂. 並可以幫助使用者處理問題.

市面上已經有部分成熟的空白頁框架,最典型的就是使用DZNEmptyDataSet.

但是其使用DZNEmptyDataSetDelegate,DZNEmptyDataSetSource來定製空白頁元素,使用時較為繁瑣.

筆者借鑑其原理的基礎上,製作了對標框架(單向對標)EmptyPage來簡化日常專案開發.

前言

EmptyPage 歷時1年, 在我司專案中穩定使用迭代6個版本,算是比較穩定.

支援UICollectionView & UITableView.

ps: 目前階段只提供 swift 版本.

EmptyPage(空白頁元件)原理與使用
EmptyPage(空白頁元件)原理與使用
EmptyPage(空白頁元件)原理與使用

實現原理

該核心部分 作為一個單獨的子庫 實現, 可使用 以下方式單獨引用.

pod 'EmptyPage/Core'
複製程式碼

具體程式碼可查閱 Github Link, 超級簡單.

  1. UIScrollView新增emptyView物件作為空白頁例項:
    public extension UIScrollView {
      public var emptyView: UIView?
    }
    複製程式碼
  2. Method Swizzling方式替換掉UITableView \ UICollectionView 中部分相關函式.以下拿UITableView 舉例:
    // DZNEmptyDataSet 對 autolayout 專案不太友好. (也可能本人沒深度使用...)
    // EmptyPage 
    // UITableView frame 變化相關函式
    open func layoutSubviews()
    open func layoutIfNeeded()
    // 資料來源增減相關函式
    open func insertRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation)
    open func deleteRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation)
    open func insertSections(_ sections: IndexSet, with animation: UITableView.RowAnimation)
    open func deleteSections(_ sections: IndexSet, with animation: UITableView.RowAnimation)
    open func reloadData()
    複製程式碼
  3. 在資料/frame變化時判斷空白頁顯示與隱藏.
    func setEmptyView(event: () -> ()) {
        oldEmptyView?.removeFromSuperview()
        event()
        guard bounds.width != 0, bounds.height != 0 else { return }
        var isHasRows = false
        let sectionCount = dataSource?.numberOfSections?(in: self) ?? numberOfSections
        for index in 0..<sectionCount {
          if numberOfRows(inSection: index) > 0 {
            isHasRows = true
            break
          }
        }
        isScrollEnabled = isHasRows
        if isHasRows {
          emptyView?.removeFromSuperview()
          return
        }
        guard let view = emptyView else{ return }
        view.frame = bounds
        addSubview(view)
        sendSubview(toBack: view)
      }
    複製程式碼
  4. 使用

    UITableView().emptyView = CustomView()
    UICollectionView().emptyView = CustomView()
    複製程式碼

    UITableView().emptyView 第一次被賦值時才會進行 Method Swizzling 相關函式.

模板檢視

DZNEmptyDataSet 的成功離不開其可高度定製化的模板檢視.但其繁瑣的 delegate apis 遠不如自定義檢視來的方便, 其對自定義檢視的支援也並不友善.

EmptyPage 優先支援 自定義檢視,並附贈 3 套可以湊合看的模板檢視(支援超級高自定義調節,但畢竟UI我們說了不算...)

採用 以下方式 則包含該部分內容:

pod 'EmptyPage'
複製程式碼
  1. 自定義檢視
    • 僅支援autolayout佈局模式

      不使用 autolayout 模式:

      1. pod 'EmptyPage/Core'

      2. UITableView().emptyView = CustomView()

    • 自定義檢視需要autolayout實現自適應高

      可以參考 內建的幾套模板檢視的約束實現.

    • 新增 EmptyPageContentViewProtocol 協議

      該協議預設實現了將自定義檢視居中約束至一個backgroundView上.

      通用性考慮: backgroundView.frame 與 tableView.frame 相同

      示例:

      class CustomView: EmptyPageContentViewProtocol{
          ...
      }
      
      let customView = CustomView()
      UITableView().emptyView = customView.mix()
      複製程式碼

      不新增該協議,可採用以下方式:

      UITableView().emptyView = EmptyPageView.mix(view: customView)

    • 檢視關係

      檢視關係

  2. 內建模板檢視

    **特性: **

    1. 支援鏈式呼叫.
    2. 元素支援高度自定義.
    3. 同樣依照自定義檢視的標準實現.

    ps: 完全等同於提前寫好的自定義模板檢視.

    • 目前可以選擇3套基本的模板檢視.
      • 文字模板(EmptyPageView.ContentView.onlyText)
      • 圖片模板(EmptyPageView.ContentView.onlyImage)
      • 混合模板(EmptyPageView.ContentView.standard)
文字模板
圖片模板
混合模板
  • 使用

    • 示例:

      UITableView().emptyView = EmptyPageView.ContentView.standard
      	.change(hspace: .button, value: 80)
      	.change(height: .button, value: 60)
      	.change(hspace: .image, value: 15)
      	.config(button: { (item) in
      		item.backgroundColor = UIColor.blue
      		item.contentEdgeInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 20)
      	})
      	.set(image: UIImage(named: "empty-1002")!)
      	.set(title: "Connection failure", color: UIColor.black, font: UIFont.boldSystemFont(ofSize: 24))
      	.set(text: "Something has gone wrong with the internet connection. Let's give it another shot.", color: UIColor.black, font: UIFont.systemFont(ofSize: 15))
      	.set(buttonTitle: "TRY AGAIN")
      	.set(tap: {
      	// 點選事件
      	})
      	.mix()
      複製程式碼
  • Apis

    模板檢視中總結起來只有三種配置函式:

    • 約束配置函式: func change(...) -> Self

      約束函式具體可配置項採用列舉的形式限定.(以免改變/衝突自適應高度相關約束)

      enum HSpaceType { } // 修改檢視水平方向上的間距

      enum VSpaceType { } // 修改檢視垂直方向上的間距

      enum HeightType { } // 修改檢視具體高度

      例如:

      standardView.change(hspace: .button, value: 80)
      			.change(height: .button, value: 60)
      複製程式碼
    • 控制元件配置函式: func set(...) -> Self

      提供了簡單的文字/字型/圖片/顏色配置.例如:

      standardView.set(title: "Connection failure", color: UIColor.black, font: UIFont.boldSystemFont(ofSize: 24))
      複製程式碼
    • 控制元件自定義配置函式: func config(element: { (element) in ... }) -> Self

      返回一個完整的控制元件,可供深度配置. 例如:

      standardView.config(button: { (item) in
      	item.backgroundColor = UIColor.blue
      	item.contentEdgeInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 20)
      	})
      複製程式碼
    • 檢視混合函式func mix():

      該函式由 EmptyPageContentViewProtocol 協議預設實現.

      作用: 將檢視約束至 backgroundView 上

      ps: 別忘了...

結尾

專案開源連結: Github/EmptyPage

個人部落格連結: 四方

相關文章