Swift Protocal實戰(zhàn)1(Refreshable)
在app的開發(fā)中,出現(xiàn)最多的一個情況就是顯示一個列表來展示數(shù)據(jù)巩剖,就像刷微博一樣,要能夠上拉加載更多,下拉進行刷新廓握。但在實際開發(fā)過程中,需要考慮的情況會更多嘁酿。我們使用
header
來表示下拉刷新控件疾棵,使用footer
來表示上拉加載控件。
在不考慮緩存的情況下單數(shù)據(jù)源tableView需要考慮以下注意點
- 首次進入這個頁面痹仙,沒有數(shù)據(jù)需要進行首次數(shù)據(jù)加載
- 首次加載過程中不能顯示footer
- 下拉刷新需要用返回的結(jié)果覆蓋數(shù)據(jù)源的數(shù)據(jù)
- 下拉刷新后需要還原footer的狀態(tài)(變更為可以加載更多)
- 上拉加載成功后需要根據(jù)返回數(shù)據(jù)數(shù)量來判斷是否還有更多數(shù)據(jù),沒有更多數(shù)據(jù)需要修改footer的狀態(tài)為
沒有更多數(shù)據(jù)了
殉了,并禁止上拉刷新功能开仰。 - 首次加載數(shù)據(jù)如果沒有網(wǎng)絡(luò)連接或者加載失敗,需要顯示一個失敗頁面,點擊失敗頁面能夠重新進行網(wǎng)絡(luò)請求獲取數(shù)據(jù)众弓。
- 每次進行下拉刷新都需要將當前page設(shè)為
1
- 每次進行上拉加載前都需要將當前page進行
+1
操作 - 每次上拉加載失敗都需要將當前page進行
-1
操作恩溅,以還原防止,下次上拉加載page多加了的問題
如果是多數(shù)據(jù)源的tableView則需要考慮的更多
- 首先就是不同數(shù)據(jù)源的page記錄谓娃,每個數(shù)據(jù)源都需要對應(yīng)一個自己的page
- 每個數(shù)據(jù)源都需要記錄是否還有更多數(shù)據(jù)可供加載
- 甚至每個數(shù)據(jù)源擁有各自的沒數(shù)據(jù)的文字提示
- 實際項目中脚乡,為了一些效果,還需要記錄當前數(shù)據(jù)源是否處于加載數(shù)據(jù)狀態(tài)滨达,以此來顯示某些加載頁面奶稠。
- 可能還會有一個個性化的定制需求
綜上所述,僅僅是一個控制器的數(shù)據(jù)加載邏輯就有這么多捡遍,如果有很多這樣類似的頁面锌订,每次都要考慮這么多問題,難免會有不少疏忽画株,而且要實現(xiàn)這些功能會產(chǎn)生大量的重復(fù)代碼辆飘,這肯定是我們不希望看到的。
分析上述需求我們發(fā)現(xiàn)谓传,實際上通常我們所接觸的tableView
大體上也就需要注意這么多問題蜈项,而且為了整個工程的統(tǒng)一性,一般情況所有的處理也是采用同一套邏輯续挟,因此我們完全可以把這些邏輯統(tǒng)一起來紧卒,使用一個Protocol
來實現(xiàn)這些邏輯。得益于Swift強大的Protocol Extention
大部分情況我們只需要在合適的關(guān)鍵點調(diào)用幾個方法就可以了庸推,所有的邏輯默認都已經(jīng)實現(xiàn)了常侦。Controller
中的代碼更少了,不相關(guān)的邏輯都封裝好了贬媒,邏輯更加簡潔了聋亡。
控制器使用代碼
class PYMyOrderListController: PYBaseViewController, Refreshable {
internal var refreshStatus: [(page: Int, isLoading: Bool, noMoreData: Bool, noMoreTitle: String)] = [(1,false,false,"沒有更多訂單了")] // 定義每個數(shù)據(jù)源需要的四個屬性,分別是當前頁碼际乘,是否被正在加載中坡倔,是否沒有更多數(shù)據(jù)可供加載了。沒有數(shù)據(jù)可供加載的footer文字
internal var currentIndex = 0 // 當前現(xiàn)實的數(shù)據(jù)源索引
internal var refreshTable: UITableView = UITableView() // 當前tableView
override func viewDidLoad() {
view.addSubview(tableView)
tableView.snp_makeConstraints { (make) in
make.left.right.bottom.equalTo(0)
make.top.equalTo(segementView.snp_bottom)
}
refreshTable = tableView // 賦值當前tableView
setupRefreshHeader() //初始化下拉刷新控件
setupRefreshFooter() // 初始化上拉加載控件
}
/// 加載數(shù)據(jù)的方法
///
/// - parameter isRefresh: 是否是下拉刷新
func refreshData(isRefresh: Bool) {
refreshStatus[currentIndex].isLoading = true // 修改當前數(shù)據(jù)源的加載狀態(tài)為正在加載
let indexItem = currentIndex
let url = URL_OrderList + "/\(type)/\(PageCount)" + "/\(refreshStatus[currentIndex].page).json"
let request = PYNetWorkTools.GET(url, hudType: .None, failer: { [weak self] (failerTuples) in
guard self != nil else{ return }
self?.loadFailer(failerTuples) // 加載失敗的方法
}) {[weak self] (response, jsonResult) in
guard self != nil else{ return }
self?.tableView.hiddenNoNetPlace()
let array = PYOrderListModel.modelArray(jsonResult)
if isRefresh {
self?.totalArray[indexItem] = array
} else {
self?.totalArray[indexItem] += array
}
self?.loadSuccess(array.count < PageCount) // 加載成功的方法脖含,并傳遞一個是否還有更多數(shù)據(jù)的返回值
}
if request != nil {
requests.append(request!)
}
}
}
以上這些代碼就可以實現(xiàn)上述所有的功能罪塔,怎么樣,是不是很有魅力呢养葵?實例中使用了Refreshable
協(xié)議征堪,這套協(xié)議可以用在UIViewController
和UITableViewController
中,其中的refreshTable
就是為了適配UIViewController
所增加的一個屬性关拒,否則連這個屬性都不用寫了佃蚜。
代碼中能看到的協(xié)議中定義的內(nèi)容如下
-
屬性
- refreshStatus
- currentInidex
- refreshTable
-
方法
- refreshData(isRefresh: Bool)
- setupRefreshHeader()
- setupRefreshFooter()
- loadFailer(failerTuples: FailerTuples)
- loadSuccess(noMoreData: Bool?)
讓我們先看看Refreshable
這個協(xié)議里是怎么寫的
/// 刷新協(xié)議
protocol Refreshable {
/// 刷新數(shù)據(jù)的方法庸娱,必須實現(xiàn),調(diào)用這個方法來執(zhí)行下拉刷新和上拉加載
///
/// - parameter isRefresh: 是否是下拉刷新
func refreshData(isRefresh: Bool)
/// 刷新狀態(tài)的四個參數(shù)谐算,分別是熟尉,當前頁碼,是否正在加載中洲脂,是否沒有更多數(shù)據(jù)了斤儿,沒有更多數(shù)據(jù)的footer顯示文字
var refreshStatus: [(page: Int, isLoading: Bool, noMoreData: Bool, noMoreTitle: String)] {set get}
/// 當前數(shù)據(jù)源的索引號
var currentIndex: Int {set get}
/// 需要處理的tableView
var refreshTable: UITableView {get set}
}
// MARK: - 遵守這個協(xié)議的是控制器
extension Refreshable where Self: UIViewController {
/// 加載數(shù)據(jù)失敗調(diào)用此方法
///
/// - parameter failerTuples: 失敗原因
mutating func loadFailer(failerTuples: (type: NetFailerType, desc: String?)?) {
refreshStatus[currentIndex].page -= 1
if refreshStatus[currentIndex].page < 0 {
refreshStatus[currentIndex].page = 0
}
refreshStatus[currentIndex].isLoading = false
refreshFooter()
if refreshTable.mj_header != nil {
refreshTable.mj_header.endRefreshing()
}
if failerTuples?.type == NetFailerType.NoNet {
if refreshTable.visibleCells.isEmpty {
refreshTable.showNoNetPlace({ [weak self] in
guard self != nil else { return }
self?.refreshData(true)
})
} else {
showToast(failerTuples?.type.rawValue ?? "")
}
}
refreshTable.reloadData()
}
/// 加載數(shù)據(jù)成功調(diào)用此方法
///
/// - parameter noMoreData: 是否沒有更多數(shù)據(jù)了
mutating func loadSuccess(noMoreData: Bool?) {
refreshStatus[currentIndex].isLoading = false
if let noMoreData = noMoreData where refreshTable.mj_footer != nil {
refreshStatus[currentIndex].noMoreData = noMoreData
refreshTable.mj_footer.hidden = false
refreshFooter()
}
if refreshTable.mj_header != nil {
refreshTable.mj_header.endRefreshing()
}
refreshTable.reloadData()
}
/// 初始化下拉刷新控件
func setupRefreshHeader() {
let header = MJRefreshNormalHeader {[weak self] () -> Void in
guard self != nil else { return }
self?.refreshTable.mj_footer.resetNoMoreData()
self?.refreshStatus[self!.currentIndex].page = 1
self?.refreshData(true)
self?.refreshStatus[self!.currentIndex].isLoading = true
}
refreshTable.mj_header = header
}
/// 初始化上拉加載控件
func setupRefreshFooter() {
let footer = MJRefreshBackStateFooter {[weak self] () -> Void in
guard self != nil else { return }
self?.refreshStatus[self!.currentIndex].page += 1
self?.refreshData(false)
self?.refreshStatus[self!.currentIndex].isLoading = true
}
footer.hidden = true
refreshTable.mj_footer = footer
}
/// 刷新上拉加載控件,用來重置上拉刷新控件狀態(tài)恐锦,控制能夠刷新以及顯示類型
func refreshFooter() {
if refreshTable.mj_footer != nil {
if refreshStatus[currentIndex].noMoreData {
refreshTable.mj_footer.endRefreshingWithNoMoreData()
} else {
refreshTable.mj_footer.endRefreshing()
}
}
}
}
整個協(xié)議簡潔明了往果,沒有一句廢話。就把眾多需要的功能及注意點都涵蓋了踩蔚。該協(xié)議具有以下特點棚放。
- 支持
UITableViewController
以及UIViewcontroller
的刷新處理。 - 支持多數(shù)據(jù)源的切換加載馅闽。
這兩個特性已經(jīng)涵蓋了日常開發(fā)中常見的所有情況飘蚯。當然你也可以只添加上拉加載,不添加下拉刷新功能福也,總之局骤,這些都隨便。