iOS MJRefresh下拉刷新(上拉加载)使用详解
下拉刷新控件目前比较火的有好几种,本人用过MJRefresh 和 SVPullToRefresh,相对而言,前者比后者可定制化、拓展新都更高一点。
因此本文着重讲一下MJRefresh的简单用法。
导入项目:
- cocoapods导入:
pod 'MJRefresh'
- 手动导入:
- 将
MJRefresh
文件夹中的所有文件拽入项目中 - 导入主头文件:
#import "MJRefresh.h"
- 将
下拉刷新:
广泛性分为6种使用场景,分别对应:默认、动画图片、隐藏时间、隐藏时间和状态、自定义文字说明、以及自定义刷新控件。
下面就各种场景分别讲一下:
1、默认场景
包含刷新菊花、下拉说明、时间
使用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #pragma mark UITableView + 下拉刷新 默认 - ( void )example01 { __weak __typeof( self ) weakSelf = self ; // 设置回调(一旦进入刷新状态就会调用这个refreshingBlock) self .tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ [weakSelf loadNewData]; }]; // 马上进入刷新状态 [ self .tableView.mj_header beginRefreshing]; } |
2、使用动画图片
PS:这里的动画并不是用gif实现的,而是利用序列帧(即若干图片组成一个不同状态下的图片数组,然后根据位置显示不同图片)去展现。
1 2 3 4 5 6 7 8 9 | #pragma mark UITableView + 下拉刷新 动画图片 - ( void )example02 { // 设置回调(一旦进入刷新状态,就调用target的action,也就是调用self的loadNewData方法) self .tableView.mj_header = [MJChiBaoZiHeader headerWithRefreshingTarget: self refreshingAction: @selector (loadNewData)]; // 马上进入刷新状态 [ self .tableView.mj_header beginRefreshing]; } |
这里用大众点评吃包子图片为例,新建一个自定义类 MJChiBaoZiHeader,继承:MJRefreshGifHeader
#import "MJRefreshGifHeader.h"
@interface MJChiBaoZiHeader : MJRefreshGifHeader
@end
然后重写prepare方法,代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | - ( void )prepare { [ super prepare]; // 设置普通状态的动画图片 NSMutableArray *idleImages = [ NSMutableArray array]; for ( NSUInteger i = 1; i<=60; i++) { UIImage *image = [UIImage imageNamed:[ NSString stringWithFormat:@ "dropdown_anim__000%zd" , i]]; [idleImages addObject:image]; } [ self setImages:idleImages forState:MJRefreshStateIdle]; // 设置即将刷新状态的动画图片(一松开就会刷新的状态) NSMutableArray *refreshingImages = [ NSMutableArray array]; for ( NSUInteger i = 1; i<=3; i++) { UIImage *image = [UIImage imageNamed:[ NSString stringWithFormat:@ "dropdown_loading_0%zd" , i]]; [refreshingImages addObject:image]; } [ self setImages:refreshingImages forState:MJRefreshStatePulling]; // 设置正在刷新状态的动画图片 [ self setImages:refreshingImages forState:MJRefreshStateRefreshing]; } |
关键点就是这里的两个图片数组,60是因为下拉控件默认拉动距离就是60距离,这里比较严谨,利用60张不同图片去对应每个距离点,当然实际中,我们可以缩减,不需要精确到每个距离点对应一张图片,这里个人自己决定。
这里需要先了解下,下拉的五种状态。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | /** 刷新控件的状态 */ typedef NS_ENUM ( NSInteger , MJRefreshState) { /** 普通闲置状态 */ MJRefreshStateIdle = 1, /** 松开就可以进行刷新的状态 */ MJRefreshStatePulling, /** 正在刷新中的状态 */ MJRefreshStateRefreshing, /** 即将刷新的状态 */ MJRefreshStateWillRefresh, /** 所有数据加载完毕,没有更多的数据了 */ MJRefreshStateNoMoreData }; |
1 | idleImages图片数组对应闲置下拉状态,表示下拉到临界值前的展示图片。 |
1 | refreshingImages图片数组对应正在刷新时的动画展示图片,一般这里需要3~5张图片去模拟动画。重写完prepare方法,就可以实现动画了。 |
3、下拉刷新 隐藏时间
这里与默认的区别就是不显示上次刷新时间,使用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #pragma mark UITableView + 下拉刷新 隐藏时间 - ( void )example03 { // 设置回调(一旦进入刷新状态,就调用target的action,也就是调用self的loadNewData方法) MJRefreshNormalHeader *header = [MJRefreshNormalHeader headerWithRefreshingTarget: self refreshingAction: @selector (loadNewData)]; // 设置自动切换透明度(在导航栏下面自动隐藏) header.automaticallyChangeAlpha = YES ; // 隐藏时间 header.lastUpdatedTimeLabel.hidden = YES ; // 马上进入刷新状态 [header beginRefreshing]; // 设置header self .tableView.mj_header = header; } |
4、下拉刷新 隐藏状态和时间
这个场景一般适用于只需要动画展示,简洁清爽,也是用的蛮多的。
同样,处理很简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #pragma mark UITableView + 下拉刷新 隐藏状态和时间 - ( void )example04 { // 设置回调(一旦进入刷新状态,就调用target的action,也就是调用self的loadNewData方法) MJChiBaoZiHeader *header = [MJChiBaoZiHeader headerWithRefreshingTarget: self refreshingAction: @selector (loadNewData)]; // 隐藏时间 header.lastUpdatedTimeLabel.hidden = YES ; // 隐藏状态 header.stateLabel.hidden = YES ; // 马上进入刷新状态 [header beginRefreshing]; // 设置header self .tableView.mj_header = header; } |
5、下拉刷新 自定义文字
想自己DIY个性文字描述,一样很简单。
不管是文字text、文字大小、还是颜色都一句话搞定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #pragma mark UITableView + 下拉刷新 自定义文字 - ( void )example05 { // 设置回调(一旦进入刷新状态,就调用target的action,也就是调用self的loadNewData方法) MJRefreshNormalHeader *header = [MJRefreshNormalHeader headerWithRefreshingTarget: self refreshingAction: @selector (loadNewData)]; // 设置文字 [header setTitle:@ "快扯我,快点" forState:MJRefreshStateIdle]; [header setTitle:@ "数据要来啦" forState:MJRefreshStatePulling]; [header setTitle:@ "服务器正在狂奔 ..." forState:MJRefreshStateRefreshing]; // 设置字体 header.stateLabel.font = [UIFont systemFontOfSize:15]; header.lastUpdatedTimeLabel.font = [UIFont systemFontOfSize:14]; // 设置颜色 header.stateLabel.textColor = [UIColor redColor]; header.lastUpdatedTimeLabel.textColor = [UIColor grayColor]; // 马上进入刷新状态 [header beginRefreshing]; // 设置刷新控件 self .tableView.mj_header = header; } |
6、下拉刷新 自定义刷新控件
上面的都不够玩,怎么办,没关系,还有最后一种更定制化的方法:自己加控件样式。
这里不限于任何控件,我们可以在头部的这片区域,尽情添加Subviews,但记住一点,高度千万不要吵过header高度(默认60)。
除了控件,甚至可以自己绘制动画等等。
实现原理:同样先自定义自己的类,继承 MJRefreshHeader
重写 prepare 方法,再重写 placeSubviews 方法 设置位置。
代码:
a、定义控件属性
1 2 3 4 5 6 | @interface MJDIYHeader() @property (weak, nonatomic ) UILabel *label; @property (weak, nonatomic ) UISwitch *s; @property (weak, nonatomic ) UIImageView *logo; @property (weak, nonatomic ) UIActivityIndicatorView *loading; @end |
b、重写prepare方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #pragma mark 在这里做一些初始化配置(比如添加子控件) - ( void )prepare { [ super prepare]; // 设置控件的高度 self .mj_h = 50; // 添加label UILabel *label = [[UILabel alloc] init]; label.textColor = [UIColor colorWithRed:1.0 green:0.5 blue:0.0 alpha:1.0]; label.font = [UIFont boldSystemFontOfSize:16]; label.textAlignment = NSTextAlignmentCenter ; [ self addSubview:label]; self .label = label; // 打酱油的开关 UISwitch *s = [[UISwitch alloc] init]; [ self addSubview:s]; self .s = s; // logo UIImageView *logo = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@ "Logo" ]]; logo.contentMode = UIViewContentModeScaleAspectFit; [ self addSubview:logo]; self .logo = logo; // loading UIActivityIndicatorView *loading = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; [ self addSubview:loading]; self .loading = loading; } |
c、重写 placeSubviews
1 2 3 4 5 6 7 8 9 10 11 12 | #pragma mark 在这里设置子控件的位置和尺寸 - ( void )placeSubviews { [ super placeSubviews]; self .label.frame = self .bounds; self .logo.bounds = CGRectMake(0, 0, self .bounds.size.width, 100); self .logo.center = CGPointMake( self .mj_w * 0.5, - self .logo.mj_h + 20); self .loading.center = CGPointMake( self .mj_w - 30, self .mj_h * 0.5); } |
d、根据下拉位移,自定义不同位移的控件展示,比如显示不同文字,颜色等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #pragma mark 监听控件的刷新状态 - ( void )setState:(MJRefreshState)state { MJRefreshCheckState; switch (state) { case MJRefreshStateIdle: [ self .loading stopAnimating]; [ self .s setOn: NO animated: YES ]; self .label.text = @ "赶紧下拉吖(开关是打酱油滴)" ; break ; case MJRefreshStatePulling: [ self .loading stopAnimating]; [ self .s setOn: YES animated: YES ]; self .label.text = @ "赶紧放开我吧(开关是打酱油滴)" ; break ; case MJRefreshStateRefreshing: [ self .s setOn: YES animated: YES ]; self .label.text = @ "加载数据中(开关是打酱油滴)" ; [ self .loading startAnimating]; break ; default : break ; } } |
上拉刷新:
上拉刷新加载和下拉很类似,这里我只讲几点小注意点:
1、MJRefreshBackNormalFooter 和 MJRefreshAutoNormalFooter都能实现上拉加载,
不过两者有点区别:前者的上拉区域在tableview的底部,后者是紧跟在数据下方。这个大家可以视情况选择。
2、如果选择的MJRefreshAutoNormalFooter,可能会遇到一个:“点击或上拉加载更多”会一直存在的问题,如何去掉呢?
解决办法:
1、修改源码,在MJRefreshConst.m里更改这句话:
NSString *const MJRefreshAutoFooterIdleText = @"";
2、设置isAutomaticallyChangeAlpha属性为yes。
参考代码:
let mjfoot = MJRefreshBackNormalFooter { if weakself?.lastId == -1 || weakself?.pageIndex == -1{ weakself?.tableView.mj_footer.endRefreshingWithNoMoreData() return } weakself?.requestData() } mjfoot?.setTitle("没有更多数据了", for: .noMoreData) mjfoot?.isAutomaticallyChangeAlpha = true tableView.mj_footer = mjfoot
3、设置没有数据时,不显示上拉加载说明
func setMJFooterVisible() { if ELList.count <= 0 { tableView.mj_footer.isHidden = true }else{ tableView.mj_footer.isHidden = false } }
4、修改刷新的提示语,这点下拉刷新和上拉加载都适用 ,再说明一下:
这种需要先初始化一个header或footer,再设置settitle方法就好了。
[header setTitle:@"快扯我,快点" forState:MJRefreshStateIdle]; [header setTitle:@"数据要来啦" forState:MJRefreshStatePulling]; [header setTitle:@"服务器正在狂奔 ..." forState:MJRefreshStateRefreshing];
下载源码:https://github.com/CoderMJLee/MJRefresh
enjoy~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架