前言
- 關于下拉刷新和上拉加載,以前開發(fā)都是直接使用第三方MJRefresh,1-2句代碼集成,使用方便,省事;最近項目上架后比較閑,就研究了一下MJ,搜集了一些資料,自己模仿微博,寫了一個刷新控件。
效果圖

效果圖.gif
應用知識點
- kvo
加載進父視圖時,注冊觀察者,實時監(jiān)聽UIScrollView的contentOffset的變化,根據(jù)變化展示不一樣的刷新狀態(tài),執(zhí)行相應的操作。
pragma mark -加入父視圖時添加觀察者
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
if (newSuperview) {
self.superScrollview = (UIScrollView *)newSuperview;
[self.superScrollview addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
}else {
if (self.superScrollview) {
[self.superScrollview removeObserver:self forKeyPath:@"contentOffset"];
}
}
}
#pragma mark-KVO的代理方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"contentOffset"]) {
if (self.superScrollview.contentInset.top==64) {
self.contentOffSetY=self.superScrollview.contentInset.top;
}
CGFloat y = self.superScrollview.contentOffset.y;
if (self.superScrollview.isDragging) {
//正在拖動
if (y< -self.contentOffSetY &&y> -self.contentOffSetY - headerRefeshHight && self.currentState ==TBStatuePulling) {
//下拉狀態(tài)->正常狀態(tài)
self.currentState = TBStatueNomal;
}else if (y <= -self.contentOffSetY - headerRefeshHight && self.currentState == TBStatueNomal)
//正常狀態(tài)->下拉狀態(tài)
{
self.currentState = TBStatuePulling;
}
}else if(self.currentState ==TBStatuePulling &&y <= -self.contentOffSetY - headerRefeshHight){ //拖拽釋放
self.currentState = TBStatueRefreshing;
}
}
}
- RunTime
Runtime 很強大,需要研究的地方也很多,這里就不多講了,這里主要用到的關聯(lián)對象。
首先來說,關聯(lián)對象,我們需要將tableView添加下拉刷新和下拉加載,系統(tǒng)并沒有這個屬性,我們需要寫個UISCrollView的類別,但我們知道Category只能添加方法,不能添加實例變量,如果只是添加屬性,其實是添加的setter和getter方法,不會自動生成實例變量的。
這時候Runtime就發(fā)揮它的作用了
.h文件
@class TBRefreshHeadView;
@class TBRefreshFootView;
@interface UIScrollView (TBRefresh)
//下拉刷新
@property(nonatomic,weak)TBRefreshHeadView *header;
//上拉加載
@property(nonatomic,weak)TBRefreshFootView *footer;
//添加下拉刷新方法
-(void)addRefreshHeaderWithBlock:(void (^)())Block;
//添加上拉刷新方法
-(void)addRefreshFootWithBlock:(void (^)())Block;
.m文件
#pragma mark-關聯(lián)頭部
-(void)setHeader:(TBRefreshHeadView *)header
{
objc_setAssociatedObject(self, @selector(header), header, OBJC_ASSOCIATION_ASSIGN);
}
-(TBRefreshHeadView*)header
{
return objc_getAssociatedObject(self, @selector(header));
}
#pragma mark-關聯(lián)底部
-(void)setFooter:(TBRefreshFootView *)footer
{
objc_setAssociatedObject(self, @selector(footer), footer, OBJC_ASSOCIATION_ASSIGN);
}
-(TBRefreshFootView*)footer
{
return objc_getAssociatedObject(self, @selector(footer));
}
#pragma mark-初始化頭部
-(void)addRefreshHeaderWithBlock:(void (^)())Block
{
TBRefreshHeadView *TBheader=[TBRefreshHeadView new];
TBheader.ReturnBlock=Block;
self.header=TBheader;
[self insertSubview:TBheader atIndex:0];
}
#pragma mark-初始化底部
-(void)addRefreshFootWithBlock:(void (^)())Block
{
TBRefreshFootView *TBfooter=[TBRefreshFootView new];
TBfooter.ReturnBlock=Block;
self.footer=TBfooter;
[self insertSubview:TBfooter atIndex:0];
}
調(diào)用
刷新頭
//開始下拉刷新
-(void)beginRefreshing;
//結(jié)束下拉刷新
-(void)endHeadRefresh;
加載底部
//結(jié)束下拉加載
- (void)endFooterRefreshing;
//沒有更多數(shù)據(jù)
-(void)NoMoreData;
//將沒有更多數(shù)據(jù)狀態(tài)設置為正常狀態(tài)
-(void)ResetNomoreData;
使用
-(UITableView*)mainTableview
{
if (!_mainTableview) {
__weak ViewController *weakself=self;
_mainTableview=[[UITableView alloc]initWithFrame:CGRectMake(0,0, self.view.frame.size.width, self.view.frame.size.height) style:UITableViewStylePlain];
_mainTableview.delegate=self;
_mainTableview.dataSource=self;
[_mainTableview addRefreshHeaderWithBlock:^{
[weakself LoadDatas];
}];
[_mainTableview addRefreshFootWithBlock:^{
[weakself LoadMoreDatas];
}];
}
return _mainTableview;
}
注意:移除觀察者
移除觀察者的時候,發(fā)現(xiàn)tableview是先于刷新頭和刷新尾釋放的,所以在tableView調(diào)用dealloc方法時,先將移除觀察者,然后將刷新頭和刷新尾置nil,發(fā)現(xiàn)dealloc方法會調(diào)用2次,查了資料發(fā)現(xiàn)。一次是 scrollView(實際tableView),另一次是UITableViewWrapperView,這個類是我們直接使用的。這種情況會出現(xiàn)在tableView上。scrollView 和 collectionView 不會出現(xiàn),所以我做了以下處理:
#pragma mark-測試可得,tableView先釋放,所以在釋放之前把頭部和尾部釋放,移除觀察者,不然會奔潰
-(void)dealloc
{
if (self.header) {
[self removeObserver:self.header forKeyPath:@"contentOffset"];
self.header=nil;
}
if (self.footer) {
[self removeObserver:self.footer forKeyPath:@"contentOffset"];
self.footer=nil;
}
}
最后
這個我第一篇簡書,寫的不好,大家多多指教!
附上github地址demo地址,ios 路還長,學習的很多,以后會繼續(xù)下去!
參考資料: