- 在做列表的下拉刷新的時(shí)候,系統(tǒng)自帶的UIRefreshControl功能不能滿足需求:示例如下
系統(tǒng)自帶UIRefreshControl.gif
- 所以自己研究了一下自定義刷新控件,當(dāng)然GitHub上面有更好用的,我只是拋磚引玉,歡迎討論
正題
- 我的需求是這樣的:
normal.png
pullDown.png
refresh.png
- 狀態(tài)枚舉
正常->下拉->刷新->刷新結(jié)束->正常
enum controlType: String {
case normal = "正常狀態(tài)"
case pullingDown = "下拉刷新"
case refreshing = "正在刷新"
}
布局
UIActivityIndicatorView只有進(jìn)行動(dòng)畫的時(shí)候才會(huì)顯示出來,所以可以和箭頭重疊布局
使用系統(tǒng)自帶的layout進(jìn)行布局,SnapKit雖然好用,但是使用的時(shí)候還要導(dǎo)入,所以pass.這里以箭頭控件舉例:
messageLabel.translatesAutoresizingMaskIntoConstraints = false
這個(gè)方法,官方文檔說明如下
When you elect to position the view using auto layout by adding your own constraints, you must set this property to NO
addConstraint(NSLayoutConstraint(item: messageLabel, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: messageLabel, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0))
獲得refreshControl的superView,添加觀察者.
判斷控件的superView是否是UIScrollView類,如果不是的話,即使添加上了也不會(huì)實(shí)現(xiàn)刷新方法.
override func willMove(toSuperview newSuperview: UIView?) {
//篩選superView,需要添加到scrollView上面
guard let view = newSuperview as? UIScrollView else {
return
}
scrollView = view
//KVO監(jiān)聽父view的contentOffset的屬性的變化
/*
NSKeyValueObservingOptionNew:提供更改前的值
NSKeyValueObservingOptionOld:提供更改后的值
NSKeyValueObservingOptionInitial:觀察最初的值(在注冊觀察服務(wù)時(shí)會(huì)調(diào)用一次觸發(fā)方法)
NSKeyValueObservingOptionPrior:分別在值修改前后觸發(fā)方法(即一次修改有兩次觸發(fā))
*/
scrollView?.addObserver(self, forKeyPath: "contentOffset", options: .new, context: nil)
}
Tip:這里也對KVO的option這個(gè)枚舉做了解釋
實(shí)現(xiàn)KVO方法
- 通過判斷superView的contentOffset來確定控件的狀態(tài),為控件添加controlType屬性,并在KVO監(jiān)聽方法中修改對應(yīng)的狀態(tài)
//比較contentOffset.y和臨界值MaxY
let contentOffsetMaxY = -(scrollView!.contentInset.top + CONTROLHEIGHT)
let contenOffsetY = scrollView!.contentOffset.y
- 如果處于用戶拖拽狀態(tài),只會(huì)顯示正常和下拉兩種狀態(tài),并且防止賦值頻繁調(diào)用,增加了判斷當(dāng)前狀態(tài)的邏輯:
- 如果在正常狀態(tài)下,超過臨界值,controlType為下拉;
- 如果在下拉狀態(tài)下,超過臨界值,controlType為正常.
if contenOffsetY >= contentOffsetMaxY && controlType == .pullingDown {
controlType = .normal
} else if contenOffsetY < contentOffsetMaxY && controlType == .normal {
controlType = .pullingDown
}
- 用戶在下拉狀態(tài)松開手,進(jìn)入刷新狀態(tài)
if controlType == .pullingDown {
controlType = .refreshing
}
在controlType的didSet方法中實(shí)現(xiàn)修改對應(yīng)的文字/執(zhí)行/停止動(dòng)畫/發(fā)送action供其他類監(jiān)聽等邏輯
- 根據(jù)枚舉獲得對應(yīng)的值
messageLabel.text = controlType.rawValue
- 刷新狀態(tài)下更改為正常狀態(tài),讓控件從刷新展示狀態(tài)回到原位
if oldValue == .refreshing {
UIView.animate(withDuration: 0.25, animations: {
self.scrollView?.contentInset.top -= CONTROLHEIGHT
self.indicatorView.stopAnimating()
}, completion: { (_) in
self.arrowImageView.isHidden = false
})
}
- 下拉旋轉(zhuǎn)箭頭方向
UIView.animate(withDuration: 0.25, animations: {
self.arrowImageView.transform = CGAffineTransform(rotationAngle: CGFloat(-3 * M_PI))
})
- 刷新狀態(tài)展示動(dòng)畫,發(fā)送消息讓外界監(jiān)聽
UIView.animate(withDuration: 0.25, animations: {
self.scrollView?.contentInset.top += CONTROLHEIGHT
self.indicatorView.startAnimating()
self.arrowImageView.isHidden = true
}, completion: { (_) in
self.sendActions(for: .valueChanged)
})
最后,附上完成圖
下拉刷新控件.gif
?GitHub下載地址:https://github.com/Corgi14/RefreshControl