一、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;
