iOS下拉、上拉刷新控件的封装

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下载链接

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

(0)

相关推荐

  • iOS上下拉刷新控件MJRefresh使用方法详解

    MJRefresh是一个好用的上下拉刷新的控件,github地址如下:https://github.com/CoderMJLee/MJRefresh很多app都使用这个控件,我们也来了解一下它的用法.下面主要是介绍在UITableView下的使用. 使用 在github上下载之后,将MJRefresh文件添加到项目中,并且在需要使用的文件上引入MJRefresh.h.然后在该文件的viewDidLoad方法中指定tableView的header和footer,如下: #import "MJRef

  • iOS编写下拉刷新控件

    现在iOS里有很多成熟的下拉刷新控件,比如MJRefresh,SVPullToRefresh 我这里参考了SV的写法,但是回调用的是代理,没有用block,个人感觉用代理更简洁一点 下拉刷新的基本原理 在scrollview的上面和下面分别添加一个view,上面的是下拉的时候展示下拉动画的headerView,下面的是上拉加载更多的时候展示动画的footerView 这里的headerView和footerView都是自己添加的,和tableView自己的header,footer不一样 hea

  • iOS表视图之下拉刷新控件功能的实现方法

    下拉刷新是重新刷新表视图或列表,以便重新加载数据,这种模式广泛用于移动平台,相信大家对于此也是非常熟悉的,那么iOS是如何做到的下拉刷新呢? 在iOS 6之后,UITableViewControl添加了一个refreshControl属性,该属性保持了UIRefreshControl的一个对象指针.UIRefreshControl就是表视图实现下拉刷新提供的类,目前该类只能用于表视图界面.下面我们就来试试该控件的使用. 编写代码之前的操作类似于前面几篇文章.代码如下: #import "View

  • 详解iOS开发中UItableview控件的数据刷新功能的实现

    实现UItableview控件数据刷新 一.项目文件结构和plist文件 二.实现效果 1.说明:这是一个英雄展示界面,点击选中行,可以修改改行英雄的名称(完成数据刷新的操作). 运行界面: 点击选中行: 修改数据后自动刷新: 三.代码示例 数据模型部分: YYheros.h文件 复制代码 代码如下: // //  YYheros.h //  10-英雄展示(数据刷新) // //  Created by apple on 14-5-29. //  Copyright (c) 2014年 itc

  • iOS下拉、上拉刷新控件的封装

    iOS 封装下拉.上拉刷新控件,首先看下效果图: 简单阐述一下:自定义头部.尾部刷新视图,继承UIView,通过KVO监听scrollView的滑动,通过偏移量设置刷新状态,通过修改状态修改scrollView的滚动位置.建一个UIScrollView的分类,添加上拉.下拉刷新及回调的方法,可以让UITableView.UICollectionView直接调用.现在很多应用是在滑动到底部自动进行上拉加载超做,可以在scrollViewDidScroll这个代理方法中手动调用尾部刷新. 下面贴上主

  • Android官方下拉刷新控件SwipeRefreshLayout使用详解

    可能开发安卓的人大多数都用过很多下拉刷新的开源组件,但是今天用了官方v4支持包的SwipeRefreshLayout觉得效果也蛮不错的,特拿出来分享. 简介: SwipeRefreshLayout组件只接受一个子组件:即需要刷新的那个组件.它使用一个侦听机制来通知拥有该组件的监听器有刷新事件发生,换句话说我们的Activity必须实现通知的接口.该Activity负责处理事件刷新和刷新相应的视图.一旦监听者接收到该事件,就决定了刷新过程中应处理的地方.如果要展示一个"刷新动画",它必须

  • Android实现支持所有View的通用的下拉刷新控件

    下拉刷新对于一个app来说是必不可少的一个功能,在早期大多数使用的是chrisbanes的PullToRefresh,或是修改自该框架的其他库.而到现在已经有了更多的选择,github上还是有很多体验不错的下拉刷新. 而下拉刷新主要有两种实现方式: 1. 在ListView中添加header和footer,监听ListView的滑动事件,动态设置header/footer的高度,但是这种方式只适用于ListView,RecyclerView. 2. 第二种方式则是继承ViewGroup或其子类,

  • Android开发之无痕过渡下拉刷新控件的实现思路详解

    相信大家已经对下拉刷新熟悉得不能再熟悉了,市面上的下拉刷新琳琅满目,然而有很多在我看来略有缺陷,接下来我将说明一下存在的缺陷问题,然后提供一种思路来解决这一缺陷,废话不多说!往下看嘞! 1.市面一些下拉刷新控件普遍缺陷演示 以直播吧APP为例: 第1种情况: 滑动控件在初始的0位置时,手势往下滑动然后再往上滑动,可以看到滑动到初始位置时滑动控件不能滑动. 原因: 下拉刷新控件响应了触摸事件,后续的一系列事件都由它来处理,当滑动控件到顶端的时候,滑动事件都被下拉刷新控件消费掉了,传递不到它的子控件

  • Android PullToRefreshLayout下拉刷新控件的终结者

    说到下拉刷新控件,网上版本有很多,很多软件也都有下拉刷新功能.有一个叫XListView的,我看别人用过,没看过是咋实现的,看这名字估计是继承自ListView修改的,不过效果看起来挺丑的,也没什么扩展性,太单调了.看了QQ2014的列表下拉刷新,发现挺好看的,我喜欢,贴一下图看一下qq的下拉刷新效果: 不错吧?嗯,是的.一看就知道实现方式不一样.咱们今天就来实现一个下拉刷新控件.由于有时候不仅仅是ListView需要下拉刷新,ExpandableListView和GridView也有这个需求,

  • Android下拉刷新控件PullToRefresh实例解析

    Android中很多时候都会用到上下拉刷新,这是一个很常用的功能,Android的v4包中也为我们提供了一种原生的下拉刷新控件--SwipeRefreshLayout,可以用它实现一个简洁的刷新效果,但今天我们的主角并不是它,而是一个很火的第三方的上下拉刷新控件--PullToRefresh.PullToRefresh包括PullToRefreshScrollView.PullToRefreshListView.PullToRefreshGridView等等很多为我们提供的控件,我们可以在xml

  • Android下拉刷新控件SwipeRefreshLayout源码解析

    SwipeRefreshLayout是Android官方的下拉刷新控件,使用简单,界面美观,不熟悉的朋友可以随便搜索了解一下,这里就不废话了,直接进入正题. 首先给张流程图吧,标出了几个主要方法的作用,可以结合着看一下哈. 这种下拉刷新控件的原理不难,基本就是监听手指的运动,获取手指的坐标,通过计算判断出是哪种操作,然后就是回调相应的接口了.SwipeRefreshLayout是继承自ViewGroup的,根据Android的事件分发机制,触摸事件应该是先传递到ViewGroup,根据onInt

随机推荐