app 顯示列表內容時, 在某一時刻可能資料為空(等待網路請求/網路請求失敗)等, 新增一個空白指示頁將有效緩解使用者可能造成的焦慮或混亂. 並可以幫助使用者處理問題.
市面上已經有部分成熟的空白頁框架,最典型的就是使用DZNEmptyDataSet.
但是其使用
DZNEmptyDataSetDelegate
,DZNEmptyDataSetSource
來定製空白頁元素,使用時較為繁瑣.筆者借鑑其原理的基礎上,製作了對標框架(單向對標)EmptyPage來簡化日常專案開發.
前言
EmptyPage 歷時1年, 在我司專案中穩定使用迭代6個版本,算是比較穩定.
支援UICollectionView & UITableView.
ps: 目前階段只提供 swift 版本.
實現原理
該核心部分 作為一個單獨的子庫 實現, 可使用 以下方式單獨引用.
pod 'EmptyPage/Core' 複製程式碼
具體程式碼可查閱 Github Link, 超級簡單.
-
為
UIScrollView
新增emptyView
物件作為空白頁例項:public extension UIScrollView { public var emptyView: UIView? } 複製程式碼
-
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() 複製程式碼
-
在資料/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) } 複製程式碼
-
使用
UITableView().emptyView = CustomView() UICollectionView().emptyView = CustomView() 複製程式碼
UITableView().emptyView 第一次被賦值時才會進行
Method Swizzling
相關函式.
模板檢視
DZNEmptyDataSet 的成功離不開其可高度定製化的模板檢視.但其繁瑣的 delegate apis 遠不如自定義檢視來的方便, 其對自定義檢視的支援也並不友善.
EmptyPage 優先支援 自定義檢視,並附贈 3 套可以湊合看的模板檢視(支援超級高自定義調節,但畢竟UI我們說了不算...)
採用 以下方式 則包含該部分內容:
pod 'EmptyPage' 複製程式碼
-
自定義檢視
-
僅支援autolayout佈局模式
不使用 autolayout 模式:
-
pod 'EmptyPage/Core'
-
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)
-
檢視關係
-
-
內建模板檢視
**特性: **
- 支援鏈式呼叫.
- 元素支援高度自定義.
- 同樣依照自定義檢視的標準實現.
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
個人部落格連結: 四方