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。

写博客的初心是希望大家共同交流成长,博主水平有限难免有偏颇之处,欢迎批评指正。

原创粉丝点击