前言
- 关于下拉刷新和上拉加载,以前开发都是直接使用第三方MJRefresh,1-2句代码集成,使用方便,省事;最近项目上架后比较闲,就研究了一下MJ,搜集了一些资料,自己模仿微博,写了一个刷新控件。
效果图
效果图.gif应用知识点
- kvo
加载进父视图时,注册观察者,实时监听UIScrollView的contentOffset的变化,根据变化展示不一样的刷新状态,执行相应的操作。
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) {
//下拉状态->正常状态
self.currentState = TBStatueNomal;
}else if (y <= -self.contentOffSetY - headerRefeshHight && self.currentState == TBStatueNomal)
//正常状态->下拉状态
{
self.currentState = TBStatuePulling;
}
}else if(self.currentState ==TBStatuePulling &&y <= -self.contentOffSetY - headerRefeshHight){ //拖拽释放
self.currentState = TBStatueRefreshing;
}
}
}
- RunTime
Runtime 很强大,需要研究的地方也很多,这里就不多讲了,这里主要用到的关联对象。
首先来说,关联对象,我们需要将tableView添加下拉刷新和下拉加载,系统并没有这个属性,我们需要写个UISCrollView的类别,但我们知道Category只能添加方法,不能添加实例变量,如果只是添加属性,其实是添加的setter和getter方法,不会自动生成实例变量的。
这时候Runtime就发挥它的作用了
.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-关联头部
-(void)setHeader:(TBRefreshHeadView *)header
{
objc_setAssociatedObject(self, @selector(header), header, OBJC_ASSOCIATION_ASSIGN);
}
-(TBRefreshHeadView*)header
{
return objc_getAssociatedObject(self, @selector(header));
}
#pragma mark-关联底部
-(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];
}
调用
刷新头
//开始下拉刷新
-(void)beginRefreshing;
//结束下拉刷新
-(void)endHeadRefresh;
加载底部
//结束下拉加载
- (void)endFooterRefreshing;
//没有更多数据
-(void)NoMoreData;
//将没有更多数据状态设置为正常状态
-(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;
}
注意:移除观察者
移除观察者的时候,发现tableview是先于刷新头和刷新尾释放的,所以在tableView调用dealloc方法时,先将移除观察者,然后将刷新头和刷新尾置nil,发现dealloc方法会调用2次,查了资料发现。一次是 scrollView(实际tableView),另一次是UITableViewWrapperView,这个类是我们直接使用的。这种情况会出现在tableView上。scrollView 和 collectionView 不会出现,所以我做了以下处理:
#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 路还长,学习的很多,以后会继续下去!
参考资料:
网友评论