iOS下拉、上拉刷新控件的封裝
iOS 封裝下拉、上拉刷新控件,首先看下效果圖:
簡單闡述一下:自定義頭部、尾部刷新視圖,繼承UIView,通過KVO監(jiān)聽scrollView的滑動,通過偏移量設(shè)置刷新狀態(tài),通過修改狀態(tài)修改scrollView的滾動位置。建一個UIScrollView的分類,添加上拉、下拉刷新及回調(diào)的方法,可以讓UITableView、UICollectionView直接調(diào)用?,F(xiàn)在很多應(yīng)用是在滑動到底部自動進(jìn)行上拉加載超做,可以在scrollViewDidScroll這個代理方法中手動調(diào)用尾部刷新。
下面貼上主要相關(guān)代碼:
控制器ViewController:
#import <UIKit/UIKit.h> @interface ViewController : UIViewController @end /*** ---------------分割線--------------- ***/ #import "ViewController.h" #import "HWRefresh.h" @interface ViewController ()<UITableViewDataSource, UITableViewDelegate> @property (nonatomic, strong) NSMutableArray *array; @property (nonatomic, strong) UITableView *tableView; @property (nonatomic, assign) NSInteger page; @end @implementation ViewController - (NSMutableArray *)array { if (!_array) { _array = [NSMutableArray array]; } return _array; } - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor blackColor]; self.page = 1; //模擬獲取信息 [self getInfo]; //創(chuàng)建控件 [self creatControl]; //添加頭部刷新 [self addHeaderRefresh]; //添加尾部刷新 [self addFooterRefresh]; } - (void)getInfo { NSArray *array = @[@"iOS HERO博客", @"iOS HERO博客", @"iOS HERO博客", @"iOS HERO博客", @"http://blog.csdn.net/hero_wqb"]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (self.page == 1) { self.array = [NSMutableArray arrayWithArray:array]; }else{ [self.array addObjectsFromArray:array]; } [_tableView reloadData]; [_tableView headerEndRefreshing]; [_tableView footerEndRefreshing]; NSLog(@"已經(jīng)刷新好了"); }); } - (void)creatControl { //列表視圖 _tableView = [[UITableView alloc] initWithFrame:CGRectMake(20, 64, [[UIScreen mainScreen] bounds].size.width - 100, [[UIScreen mainScreen] bounds].size.height - 164) style:UITableViewStylePlain]; _tableView.dataSource = self; _tableView.delegate = self; [self.view addSubview:_tableView]; } - (void)addHeaderRefresh { __weak typeof(self) weakSelf = self; [_tableView addHeaderRefreshWithCallback:^{ __strong typeof(weakSelf) strongSelf = weakSelf; strongSelf.page = 1; [strongSelf getInfo]; }]; } - (void)addFooterRefresh { __weak typeof(self) weakSelf = self; [_tableView addFooterRefreshWithCallback:^{ __strong typeof(weakSelf) strongSelf = weakSelf; strongSelf.page ++; [strongSelf getInfo]; }]; } #pragma mark - UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.array.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *identifier = @"refreshTest"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; } cell.textLabel.text = [_array[indexPath.row] stringByAppendingString:[NSString stringWithFormat:@"_%ld", indexPath.row]]; return cell; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { //滑動到底部自動刷新 if (_tableView.contentSize.height > _tableView.frame.size.height && _tableView.contentOffset.y + _tableView.frame.size.height > _tableView.contentSize.height - 40 && _page < 50) { [_tableView footerBeginRefreshing]; } } @end
刷新基類HWRefreshBaseView:
#import <UIKit/UIKit.h> #define HWRefreshContentOffset @"contentOffset" typedef enum { HWRefreshStateNormal = 0, //普通狀態(tài) HWRefreshStatePulling, //釋放即可刷新的狀態(tài) HWRefreshStateRefreshing, //正在刷新中的狀態(tài) } HWRefreshState; @interface HWRefreshBaseView : UIView @property (nonatomic, weak) UIScrollView *scrollView; @property (nonatomic, copy) NSString *pullToRefreshText; @property (nonatomic, copy) NSString *releaseToRefreshText; @property (nonatomic, copy) NSString *refreshingText; @property (nonatomic, copy) void (^refreshingCallback)(); @property (nonatomic, assign) HWRefreshState state; @property (nonatomic, assign) UIEdgeInsets scrollViewOriginalInset; - (void)beginRefreshing; - (void)endRefreshing; @end /*** ---------------分割線--------------- ***/ #import "HWRefreshBaseView.h" #define KHWRefreshViewHeight 44.0f #define KImageW 30.0f #define KLabelW 100.0f @interface HWRefreshBaseView () @property (nonatomic, weak) UILabel *rLabel; @property (nonatomic, weak) UIImageView *rImageView; @end @implementation HWRefreshBaseView - (instancetype)initWithFrame:(CGRect)frame { frame.size.height = KHWRefreshViewHeight; if (self = [super initWithFrame:frame]) { CGFloat imageH = 30.f; CGFloat labelH = 20.f; CGFloat imageX = ([UIScreen mainScreen].bounds.size.width - KImageW - KLabelW) * 0.5; CGFloat imageY = (KHWRefreshViewHeight - imageH) * 0.5; CGFloat labelY = (KHWRefreshViewHeight - labelH) * 0.5; //圖片 UIImageView *rImageView = [[UIImageView alloc] initWithFrame:CGRectMake(imageX, imageY, KImageW, imageH)]; rImageView.image = [UIImage imageNamed:@"refreshing.jpg"]; [self addSubview:rImageView]; self.rImageView = rImageView; //標(biāo)簽 UILabel *rLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(rImageView.frame), labelY, KLabelW, labelH)]; rLabel.text = self.pullToRefreshText; rLabel.font = [UIFont systemFontOfSize:14.0f]; rLabel.textAlignment = NSTextAlignmentCenter; [self addSubview:rLabel]; self.rLabel = rLabel; } return self; } - (void)willMoveToSuperview:(UIView *)newSuperview { [super willMoveToSuperview:newSuperview]; //舊的父控件 [self.superview removeObserver:self forKeyPath:HWRefreshContentOffset context:nil]; //新的父控件 if (newSuperview) { [newSuperview addObserver:self forKeyPath:HWRefreshContentOffset options:NSKeyValueObservingOptionNew context:nil]; //記錄UIScrollView _scrollView = (UIScrollView *)newSuperview; //記錄UIScrollView最開始的contentInset _scrollViewOriginalInset = _scrollView.contentInset; } //居中顯示圖片、提示信息 CGRect temFrame = _rImageView.frame; temFrame.origin.x = (newSuperview.frame.size.width - KImageW - KLabelW) * 0.5; _rImageView.frame = temFrame; CGRect tf = _rLabel.frame; tf.origin.x = CGRectGetMaxX(_rImageView.frame); _rLabel.frame = tf; } - (void)setPullToRefreshText:(NSString *)pullToRefreshText { _pullToRefreshText = pullToRefreshText; self.rLabel.text = pullToRefreshText; } - (void)setState:(HWRefreshState)state { if (_state == state) return; switch (state) { case HWRefreshStateNormal: { [self stopAnimating]; self.rLabel.text = self.pullToRefreshText; break; } case HWRefreshStatePulling: { self.rLabel.text = self.releaseToRefreshText; break; } case HWRefreshStateRefreshing: { [self startAnimating]; self.rLabel.text = self.refreshingText; if (self.refreshingCallback) self.refreshingCallback(); break; } default: break; } _state = state; } //開始刷新 - (void)beginRefreshing { self.state = HWRefreshStateRefreshing; } //結(jié)束刷新 - (void)endRefreshing { self.state = HWRefreshStateNormal; } //開始動畫 - (void)startAnimating { NSMutableArray *array = [NSMutableArray array]; for (int i = 0; i < 2; i++) { NSString *imageName = [NSString stringWithFormat:@"refreshing%02d.jpg", i + 1]; UIImage *image = [UIImage imageNamed:imageName]; [array addObject:image]; } [_rImageView setAnimationImages:array]; [_rImageView setAnimationDuration:0.3f]; [_rImageView startAnimating]; } //結(jié)束動畫 - (void)stopAnimating { if (_rImageView.isAnimating) { [_rImageView stopAnimating]; [_rImageView performSelector:@selector(setAnimationImages:) withObject:nil afterDelay:0]; } } @end
頭部刷新HWRefreshHeader:
#import "HWRefreshBaseView.h" @interface HWRefreshHeader : HWRefreshBaseView + (instancetype)header; @end /*** ---------------分割線--------------- ***/ #import "HWRefreshHeader.h" @implementation HWRefreshHeader + (instancetype)header { return [[HWRefreshHeader alloc] init]; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.pullToRefreshText = @"下拉即可刷新"; self.releaseToRefreshText = @"釋放即可刷新"; self.refreshingText = @"刷新中..."; } return self; } - (void)willMoveToSuperview:(UIView *)newSuperview { [super willMoveToSuperview:newSuperview]; //設(shè)置自己的位置和尺寸 CGRect frame = self.frame; frame.origin.y = - self.frame.size.height; self.frame = frame; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { //不能跟用戶交互或正在刷新就直接返回 if (!self.userInteractionEnabled || self.alpha <= 0.01 || self.hidden || self.state == HWRefreshStateRefreshing) return; //根據(jù)偏移量設(shè)置相應(yīng)狀態(tài) if ([keyPath isEqualToString:HWRefreshContentOffset]) { [self setStateWithContentOffset]; } } - (void)setStateWithContentOffset { //當(dāng)前的contentOffset CGFloat currentOffsetY = self.scrollView.contentOffset.y; //頭部控件剛好出現(xiàn)的offsetY CGFloat happenOffsetY = - self.scrollViewOriginalInset.top; //如果是向上滾動到看不見頭部控件,直接返回 if (currentOffsetY >= happenOffsetY) return; //滑動時 if (self.scrollView.isDragging) { //普通狀態(tài)和即將刷新狀態(tài)的臨界點 CGFloat normalTopullingOffsetY = happenOffsetY - self.frame.size.height; //轉(zhuǎn)為即將刷新狀態(tài) if (self.state == HWRefreshStateNormal && currentOffsetY < normalTopullingOffsetY) { self.state = HWRefreshStatePulling; //轉(zhuǎn)為普通狀態(tài) }else if (self.state == HWRefreshStatePulling && currentOffsetY >= normalTopullingOffsetY) { self.state = HWRefreshStateNormal; } //松手時,如果是松開就可以進(jìn)行刷新的狀態(tài),則進(jìn)行刷新 }else if (self.state == HWRefreshStatePulling) { self.state = HWRefreshStateRefreshing; } } - (void)setState:(HWRefreshState)state { //若狀態(tài)未改變,直接返回 if (self.state == state) return; //保存舊狀態(tài) HWRefreshState oldState = self.state; //調(diào)用父類方法 [super setState:state]; switch (state) { case HWRefreshStateNormal: { //如果由刷新狀態(tài)返回到普通狀態(tài) if (oldState == HWRefreshStateRefreshing) { [UIView animateWithDuration:0.25f animations:^{ UIEdgeInsets inset = self.scrollView.contentInset; inset.top -= self.frame.size.height; self.scrollView.contentInset = inset; }]; } break; } case HWRefreshStatePulling: { break; } case HWRefreshStateRefreshing: { //執(zhí)行動畫 [UIView animateWithDuration:0.25f animations:^{ CGFloat top = self.scrollViewOriginalInset.top + self.frame.size.height; //增加滾動區(qū)域 UIEdgeInsets inset = self.scrollView.contentInset; inset.top = top; self.scrollView.contentInset = inset; //設(shè)置滾動位置 CGPoint offset = self.scrollView.contentOffset; offset.y = - top; self.scrollView.contentOffset = offset; }]; break; } default: break; } self.state = state; } @end
分類UIScrollView+HWRefresh:
#import <UIKit/UIKit.h> @interface UIScrollView (HWRefresh) //添加下拉刷新回調(diào) - (void)addHeaderRefreshWithCallback:(void (^)())callback; //讓下拉刷新控件停止刷新 - (void)headerEndRefreshing; //添加上拉刷新回調(diào) - (void)addFooterRefreshWithCallback:(void (^)())callback; //讓上拉刷新控件開始刷新 - (void)footerBeginRefreshing; //讓上拉刷新控件停止刷新 - (void)footerEndRefreshing; @end /*** ---------------分割線--------------- ***/ #import "UIScrollView+HWRefresh.h" #import "HWRefreshHeader.h" #import "HWRefreshFooter.h" #import <objc/runtime.h> @interface UIScrollView () @property (nonatomic, weak) HWRefreshHeader *header; @property (weak, nonatomic) HWRefreshFooter *footer; @end @implementation UIScrollView (HWRefresh) static char HWRefreshHeaderKey; static char HWRefreshFooterKey; - (void)setHeader:(HWRefreshHeader *)header { [self willChangeValueForKey:@"HWRefreshHeaderKey"]; objc_setAssociatedObject(self, &HWRefreshHeaderKey, header, OBJC_ASSOCIATION_ASSIGN); [self didChangeValueForKey:@"HWRefreshHeaderKey"]; } - (HWRefreshHeader *)header { return objc_getAssociatedObject(self, &HWRefreshHeaderKey); } - (void)setFooter:(HWRefreshFooter *)footer { [self willChangeValueForKey:@"HWRefreshFooterKey"]; objc_setAssociatedObject(self, &HWRefreshFooterKey, footer, OBJC_ASSOCIATION_ASSIGN); [self didChangeValueForKey:@"HWRefreshFooterKey"]; } - (HWRefreshFooter *)footer { return objc_getAssociatedObject(self, &HWRefreshFooterKey); } - (void)addHeaderRefreshWithCallback:(void (^)())callback { if (!self.header) { HWRefreshHeader *header = [HWRefreshHeader header]; [self addSubview:header]; self.header = header; } self.header.refreshingCallback = callback; } - (void)headerEndRefreshing { [self.header endRefreshing]; } - (void)addFooterRefreshWithCallback:(void (^)())callback { if (!self.footer) { HWRefreshFooter *footer = [HWRefreshFooter footer]; [self addSubview:footer]; self.footer = footer; } self.footer.refreshingCallback = callback; } - (void)footerBeginRefreshing { [self.footer beginRefreshing]; } - (void)footerEndRefreshing { [self.footer endRefreshing]; } @end
寫博客的初心是希望大家共同交流成長,博主水平有限難免有偏頗之處,歡迎批評指正。
相關(guān)文章
解析Objective-C?中?`+load`?方法的執(zhí)行順序
在?Objective-C?中,+load?方法是在類或分類被加載到內(nèi)存時調(diào)用的,它在程序啟動過程中非常早的階段執(zhí)行,用于在類或分類被加載時進(jìn)行一些初始化工作,這篇文章主要介紹了?Objective-C?中?`+load`?方法的執(zhí)行順序,需要的朋友可以參考下2024-07-07在iOS App中實現(xiàn)地理位置定位的基本方法解析
這篇文章主要介紹了在iOS App中實現(xiàn)地理位置定位的基本方法解析,包括獲取當(dāng)前位置和計算兩點間距離等基本功能的實現(xiàn),需要的朋友可以參考下2016-05-05iOS App開發(fā)中使用及自定義UITableViewCell的教程
這篇文章主要介紹了iOS App開發(fā)中使用及自定義UITableViewCell的教程,自定義TableViewCell文中使用Objective-C演示而非ib,需要的朋友可以參考下2016-04-04