iOS 封装下拉、上拉刷新控件 —— HERO博客
来源:互联网 发布:软件设计师教程 编辑:程序博客网 时间:2024/05/29 15:56
iOS 封装下拉、上拉刷新控件,首先看下效果图:
简单阐述一下:自定义头部、尾部刷新视图,继承UIView,通过KVO监听scrollView的滑动,通过偏移量设置刷新状态,通过修改状态修改scrollView的滚动位置。建一个UIScrollView的分类,添加上拉、下拉刷新及回调的方法,可以让UITableView、UICollectionView直接调用。现在很多应用是在滑动到底部自动进行上拉加载超做,可以在scrollViewDidScroll这个代理方法中手动调用尾部刷新。
下面贴上主要相关代码:
控制器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]; //创建控件 [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(@"已经刷新好了"); });}- (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, //普通状态 HWRefreshStatePulling, //释放即可刷新的状态 HWRefreshStateRefreshing, //正在刷新中的状态} 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; //标签 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;}//结束刷新- (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];}//结束动画- (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]; //设置自己的位置和尺寸 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; //根据偏移量设置相应状态 if ([keyPath isEqualToString:HWRefreshContentOffset]) { [self setStateWithContentOffset]; }}- (void)setStateWithContentOffset{ //当前的contentOffset CGFloat currentOffsetY = self.scrollView.contentOffset.y; //头部控件刚好出现的offsetY CGFloat happenOffsetY = - self.scrollViewOriginalInset.top; //如果是向上滚动到看不见头部控件,直接返回 if (currentOffsetY >= happenOffsetY) return; //滑动时 if (self.scrollView.isDragging) { //普通状态和即将刷新状态的临界点 CGFloat normalTopullingOffsetY = happenOffsetY - self.frame.size.height; //转为即将刷新状态 if (self.state == HWRefreshStateNormal && currentOffsetY < normalTopullingOffsetY) { self.state = HWRefreshStatePulling; //转为普通状态 }else if (self.state == HWRefreshStatePulling && currentOffsetY >= normalTopullingOffsetY) { self.state = HWRefreshStateNormal; } //松手时,如果是松开就可以进行刷新的状态,则进行刷新 }else if (self.state == HWRefreshStatePulling) { self.state = HWRefreshStateRefreshing; }}- (void)setState:(HWRefreshState)state{ //若状态未改变,直接返回 if (self.state == state) return; //保存旧状态 HWRefreshState oldState = self.state; //调用父类方法 [super setState:state]; switch (state) { case HWRefreshStateNormal: { //如果由刷新状态返回到普通状态 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: { //执行动画 [UIView animateWithDuration:0.25f animations:^{ CGFloat top = self.scrollViewOriginalInset.top + self.frame.size.height; //增加滚动区域 UIEdgeInsets inset = self.scrollView.contentInset; inset.top = top; self.scrollView.contentInset = inset; //设置滚动位置 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)//添加下拉刷新回调- (void)addHeaderRefreshWithCallback:(void (^)())callback;//让下拉刷新控件停止刷新- (void)headerEndRefreshing;//添加上拉刷新回调- (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
Demo下载链接http://code.cocoachina.com/view/135381。
写博客的初心是希望大家共同交流成长,博主水平有限难免有偏颇之处,欢迎批评指正。
阅读全文
1 0
- iOS 封装下拉、上拉刷新控件 —— HERO博客
- Android自定义控件实战——实现仿IOS下拉刷新上拉加载 PullToRefreshLayout
- iOS 下拉刷新 上拉刷新 MJRefresh
- iOS 自定义下拉选项框 —— HERO博客
- iOS 封装加载、提示窗progressHUD —— HERO博客
- 下拉刷新,上拉加载控件
- ListView 上拉和下拉刷新控件
- 上拉加载-下拉刷新控件 RefreshListView
- 上拉刷新下拉加载控件-PullToRefresh
- 上拉加载下拉刷新控件
- RecyclerView下拉刷新上拉加载(三)—对Adapter的封装
- IOS Table 下拉,上拉刷新数据
- ios 上拉加载下拉刷新Dome
- ios 下拉刷新上拉加载集成。
- iOS 上拉加载和下拉刷新
- iOS 集成下拉刷新上拉加载
- iOS:上拉刷新,下拉加载
- iOS 开发:上拉加载,下拉刷新
- 如何查看JDK以及JAVA框架的源码
- node.js项目linux下退出终端服务停止问题
- Spring 测试
- 如何让height:100%起作用
- BFC原理及其应用
- iOS 封装下拉、上拉刷新控件 —— HERO博客
- 数据库操作如:插入操作,批处理与循环逐个插入性能比较
- java利用DecimalFormat保留小数点后两位,不足用0补齐
- Linux 学习笔记(二)文件管理
- 【Dubbo分布式服务框架】3.基于注解的服务提供者和消费者
- 利用表格建立如下网页(歌曲自选,至少选择3首歌曲)。
- linux下配置SSH免密码连接
- Maven项目下的Spring Quartz简单集成
- AngularJS—学习 ui-router