一、UITableViewCell详解
二、自定义刷新控件步骤
①偏移量判断
②界面编写
③增加控件
④切换状态:初始-下拉刷新->header完全出现时开始刷新->数据获取完成时结束刷新(开始刷新-正在刷新-结束刷新)
⑤封装代码
⑥自动刷新和重复刷新
代码实现:
// // ViewController.m // HKUtilities // // Created by HuJinTao on 2018/10/24. // Copyright © 2018 HuJinTao. All rights reserved. // #import "ViewController.h" #import "HKMacro.h" @interface ViewController ()<UITableViewDelegate,UITableViewDataSource> /** 数据量 */ @property (nonatomic, assign) NSInteger dataCount; /** tableView列表 */ @property (nonatomic, strong) UITableView * tableView ; /** 上拉加载更多控件 */ @property (nonatomic, strong) UIView *footer; /** 上拉加载更多控件里面的文字 */ @property (nonatomic, strong) UILabel *footerLabel; /** 上拉加载更多控件时是否正在刷新 */ @property (nonatomic, assign, getter=isFooterRefreshing) BOOL footerRefreshing ; /** 下拉刷新控件 */ @property (nonatomic, strong) UIView *header; /** 下拉刷新控件里面的文字 */ @property (nonatomic, strong) UILabel *headerLabel; /** 下拉加载更多控件时正在刷新 */ @property (nonatomic, assign, getter=isHeaderRefreshing) BOOL headerRefreshing ; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.dataCount = 3; self.navigationItem.title = @"自定义刷新控件"; self.view.backgroundColor = kLightGrayColor; self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, HK_NAVBAR_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT-HK_NAVBAR_HEIGHT) style:UITableViewStylePlain]; [self.view addSubview:self.tableView]; self.tableView.delegate = self; self.tableView.dataSource = self; [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"]; [self setupRefresh]; self.tableView.scrollIndicatorInsets = self.tableView.contentInset;//设置滚动条的contentInset self.tableView.contentInset = UIEdgeInsetsMake(0, 0, HK_TABBAR_HEIGHT, 0); } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { //根据数据量设置footer是否显示(有数据显示,无数据隐藏) self.footer.hidden = (self.dataCount == 0); return self.dataCount; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; cell.textLabel.text = [NSString stringWithFormat:@"自定义刷新控件-%ld",(long)indexPath.row]; return cell; } - (void)setupRefresh { //FIXME:②界面编写 //广告条 UILabel * label = [UILabel new]; label.backgroundColor = kBlackColor; label.textColor = kWhiteColor; label.text = @"广告"; label.textAlignment = NSTextAlignmentCenter; label.frame = CGRectMake( 0, 0, 0, 30); self.tableView.tableHeaderView = label; //header UIView *header = [[UIView alloc] init]; header.frame = CGRectMake(0, -50, self.tableView.width, 50); self.header = header; //FIXME:③增加控件 [self.tableView addSubview:header]; UILabel *headerLabel = [[UILabel alloc] init]; headerLabel.frame = CGRectMake(0, 0, header.width, header.height); headerLabel.backgroundColor = kRedColor; headerLabel.text = @"下拉可以刷新"; headerLabel.backgroundColor = kRedColor; headerLabel.textColor = kWhiteColor; headerLabel.textAlignment = NSTextAlignmentCenter; [header addSubview:headerLabel]; self.headerLabel = headerLabel; //FIXME:不建议使用此属性 //self.tableView.tableHeaderView = header; //footer self.footer = [[UIView alloc] init]; self.footer.frame = CGRectMake(0, 0, self.tableView.width, 35); self.footerLabel = [[UILabel alloc] init]; self.footerLabel.frame = CGRectMake(0, 0, self.footer.width, 35); self.footerLabel.backgroundColor = kRedColor; self.footerLabel.text = @"上拉可以加载更多"; self.footerLabel.textColor = kWhiteColor; self.footerLabel.textAlignment = NSTextAlignmentCenter; [self.footer addSubview:self.footerLabel]; self.tableView.tableFooterView = self.footer; } - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { return 35; } #pragma mark---------代理方法------ /** 用户松开scrollView时调用 */ -(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { //如果正在下拉刷新,直接返回 if (self.isHeaderRefreshing) return; //当scrollView的偏移量y值 >= offsetY时,代表header已经完全出现 CGFloat offsetY = -(self.tableView.contentInset.top + self.header.height); if (self.tableView.contentOffset.y <= offsetY) { //header开始刷新 //FIXME:⑤封装代码 [self headerBeginRefreshing]; } } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { //处理Header [self dealHeader]; //处理Footer [self dealFooter]; } /** 处理Hooter */ - (void)dealHeader { //如果正在下拉刷新,直接返回 if (self.isHeaderRefreshing) return; //FIXME:④切换状态 //当scrollView的偏移量y值 >= offsetY时,代表footer已经完全出现 CGFloat offsetY = -(self.tableView.contentInset.top + self.header.height); if (self.tableView.contentOffset.y <= offsetY) { //header已经完全出现 self.headerLabel.text = @"松开立即刷新"; self.headerLabel.backgroundColor = kGrayColor; }else { self.headerLabel.text = @"下拉可以刷新"; self.headerLabel.backgroundColor = kRedColor; } } /** 处理footer */ - (void)dealFooter{ //如果没有内容,则不需要执行这个操作 if (self.tableView.contentSize.height == 0) return; //如果正在刷新,直接返回 if (self.isFooterRefreshing) return; //FIXME:①偏移量判断 //当scrollView的偏移量y值 >= offsetY时,代表footer已经完全出现 //A.footer 完全出现时的偏移量 //scrollView.contentOffset.y = 内容高度+底部内边距-frame高度 //CGFloat ofsetY = self.tableView.contentSize.height + self.tableView.contentInset.bottom; //B.假设出现一半的时候修改状态,变为正在加载中... // CGFloat ofsetY = self.tableView.contentSize.height + self.tableView.contentInset.bottom - self.tableView.height-self.tableView.tableFooterView.height/2; //C.刚出现就修改状态 CGFloat ofsetY = self.tableView.contentSize.height + self.tableView.contentInset.bottom - self.tableView.height-self.tableView.tableFooterView.height; if (self.tableView.contentOffset.y >= ofsetY && self.tableView.contentOffset.y > -(self.tableView.contentInset.top)) { //footer已经完全出现,并且是往下拖拽 //进入刷新状态 //FIXME:⑤封装代码 [self footerBeginRefreshing]; } } #pragma mark - header - (void)headerBeginRefreshing { //如果正在刷新,直接返回 if (self.isHeaderRefreshing) return; //header已经完全出现 self.headerLabel.text = @"正在刷新数据..."; self.headerLabel.backgroundColor = kBlueColor; self.headerRefreshing = YES; //增加内边距 [UIView animateWithDuration:0.25 animations:^{ UIEdgeInsets inset = self.tableView.contentInset; inset.top += self.header.height; self.tableView.contentInset = inset; //FIXME:⑥自动刷新和重复刷新 //修改偏移量(稳住刷新状态) self.tableView.contentOffset = CGPointMake(self.tableView.contentOffset.x, - inset.top); }]; HKLog(@"发送请求给服务器。下拉刷新数据"); //发送请求给服务器。下拉刷新数据 [self loadNewData]; } - (void)headerEndRefreshing { self.headerRefreshing = NO; //减小内边距 [UIView animateWithDuration:0.25 animations:^{ UIEdgeInsets inset = self.tableView.contentInset; inset.top -= self.header.height; self.tableView.contentInset = inset; }]; //self.headerLabel.text = @"下拉可以刷新"; //self.headerLabel.backgroundColor = kRedColor; } #pragma mark - footer - (void)footerBeginRefreshing { //如果正在刷新,直接返回 if (self.isFooterRefreshing) return;//保证同一时间只执行一次 //进入刷新状态 self.footerRefreshing = YES; self.footerLabel.text = @"正在加载更多数据..."; self.footerLabel.backgroundColor = kBlueColor; //发送请求给服务器-加载更多数据 HKLog(@"发送请求给服务器-加载更多数据"); [self loadMoreData]; } - (void)footerEndRefreshing { self.footerRefreshing = NO; self.footerLabel.text = @"上拉可以加载更多"; self.footerLabel.backgroundColor = kRedColor; } #pragma mark - 数据处理 /** 发送请求给服务器。下拉刷新数据 */ - (void)loadNewData { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //服务器的数据回来了 self.dataCount = 3; [self.tableView reloadData]; //结束刷新 [self headerEndRefreshing]; }); } /** 发送请求给服务器-加载更多数据 */ - (void)loadMoreData { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //服务器请求回来 self.dataCount +=5; [self.tableView reloadData]; //结束刷新 [self footerEndRefreshing]; }); } @end
MethodList
效果图
三、第三方MJRefresh类结构图
MJRefresh 类结构图:
MIRefreshComponent:刷新控件的基类
MJRefreshHeader:基础的下拉刷新控件(Header)
MJRefreshStateHeader:带有状态文字的下拉刷新控件
MJRefreshNormalHeader:默认的下拉刷新控件
MJRefreshGitHeader:带动图的下拉刷新控件
MJRefreshFooter:基础的下拉刷新控件(Footer)
MJRefreshBackFooter:会回弹到底部的下拉刷新控件
MJRefreshAutoFooter:会自动刷新的上拉刷新控件
MJRefreshBackStateFooter:带有状态文字的上拉加载控件MJRefreshAutoStateFooter:带有状态文字的上拉加载控件
MIRefreshBackNormalFooter:默认的上拉加载控件
MIRefreshBackGifFooter:带动图的上拉加载控件
MIRefreshAutoNormalFooter:默认的上拉加载控件
MIRefreshAutoGifFooter:带动图的上拉加载控件
使用:
+ (MJRefreshGifHeader *)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock{
MJRefreshGifHeader *header = [MJRefreshGifHeader headerWithRefreshingBlock:refreshingBlock];
header.lastUpdatedTimeLabel.hidden = YES;
header.stateLabel.hidden = YES;
NSMutableArray *a = @[].mutableCopy;
for (int i =0 ; i<4; i++) {
[a addObject:[UIImage imageNamed:[NSString stringWithFormat:@"下啦刷新-%d",i+1]]];
}
[header setImages:a duration:0.5f forState:MJRefreshStateRefreshing];
[header setImages:@[[UIImage imageNamed:@"松手加载-1"],[UIImage imageNamed:@"松手加载-2"]] duration:0.3f forState:MJRefreshStatePulling];
[header setImages:@[[UIImage imageNamed:@"用力一点-1"],[UIImage imageNamed:@"用力一点-2"]] duration:0.5f forState:MJRefreshStateIdle];
header.backgroundColor = [UIColor clearColor];
return header;
}
+ (MJRefreshAutoNormalFooter *)footerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock{
MJRefreshAutoNormalFooter *footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:refreshingBlock];
footer.backgroundColor = [UIColor clearColor];
footer.automaticallyRefresh = YES;
footer.triggerAutomaticallyRefreshPercent = -3.0;
return footer;
}
_collectionView = [[XBHomeCollectionView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)];
_collectionView.homeDelegate = self;
_collectionView.backgroundColor = kBlackColor;
XBWeakSelf
_collectionView.mj_header = [XBTool headerWithRefreshingBlock:^{
XBStrongSelf
[self reloadData:NO];
}];
_collectionView.mj_footer = [XBTool footerWithRefreshingBlock:^{
XBStrongSelf
[self reloadData:YES];
}];
[self.view addSubview:_collectionView];
self.collectionView.mj_footer.hidden = YES;
-(void)reloadData:(BOOL)append {
if (!append) {
self.currentPage = 1;
[_dataArray removeAllObjects];
}else {
self.currentPage += 1;
}
[self getData:append];
}
- (void)getData:(BOOL)append{
XBWeakSelf
//获取精选列表
NSDictionary * dic = @{@"currentPage":[NSString stringWithFormat:@"%ld",(long)self.currentPage],
@"pageSize":[NSString stringWithFormat:@"%ld",(long)DefaultPageSize]
};
[[HKNetService jsonManager] sendPOSTWithUrl:SERVER_HOMEHANDPICKLIST params:dic success:^(HKURLResponse *response, HKResultMessage *resultMessage) {
XBStrongSelf
NSDictionary * dic = response.result;
NSArray * array = [dic valueForKey:@"data"];
for (NSDictionary *dic in array) {
XBHomeModel * model = [[XBHomeModel alloc] init];
[model setValuesForKeysWithDictionary:dic];
[self.dataArray addObject:model];
}
if ([response.responseData isKindOfClass:[NSDictionary class]] || [response.responseData isKindOfClass:[NSArray class]]) {
NSString *b = [response.responseData JF_jsonString];
[XBUserDefaults setObject:b forKey:HandpickListData];
[XBUserDefaults synchronize];
}
[self endRefreshing];
if (self.dataArray.count) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView setDataArray:self.dataArray];
});
}
if (array.count<DefaultPageSize) {
self.collectionView.mj_footer.hidden = YES;
}
if (array.count == 0) {
[self.collectionView.mj_footer endRefreshingWithNoMoreData];
}
} failure:^(HKURLResponse *response, HKResultMessage *resultMessage) {
XBStrongSelf
[self endRefreshing];
[HKToast showErrorMessage:resultMessage];
}];
}
- (void)endRefreshing {
[self.collectionView.mj_header endRefreshing];
[self.collectionView.mj_footer endRefreshing];
}
下拉刷新-动画图片
// 设置回调(一旦进入刷新状态,就调用target的action,也就是调用self的loadNewData方法)
MJRefreshGifHeader *header = [MJRefreshGifHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewData)]; // 设置普通状态的动画图片 [header setImages:idleImages forState:MJRefreshStateIdle]; // 设置即将刷新状态的动画图片(一松开就会刷新的状态) [header setImages:pullingImages forState:MJRefreshStatePulling]; // 设置正在刷新状态的动画图片 [header setImages:refreshingImages forState:MJRefreshStateRefreshing]; // 设置header self.tableView.mj_header = header;