UITableView:下拉刷新和上拉加载更多 - cDigger

时间 2013-11-24 02:00:00  博客园精华区
主题 UITableView

【转载请注明出处】

本文将说明让UIScrollView支持"下拉刷新"和"上拉加载更多"的实现机制,并实现一个可用的tableView子类,以下主要以"下拉刷新"进行说明。

工程地址在帖子最下方,只需要代码的直拉到底即可。

1、contentInset和下拉刷新

contentInset是UIScrollView的属性,它描述了UIScrollView的内容View的内边距,具体可见官方文档:

Scroll View programming Guide for iOS

目前几乎所有"下拉刷新"的第三方库都是依赖它实现的。

【为便于讨论,将下拉刷新/上拉加载时显示的视图称为refresh panel,如下图】 

在用户手指向下滑动到最终更新界面的过程中,经历了4个步骤:

(1)随着用户下拉逐渐显示UITableView顶部的refresh panel;

(2a)下拉达到预设位置,状态文字变为"松开可以刷新";

(2b)下拉未达到预设位置,用户手指离开屏幕,ScrollView弹回,refresh panel重新隐藏起来,结束。

(3)用户手指离开屏幕,refresh panel保持显示。状态文字变为"加载中",在后台执行更新数据的操作;

(4)数据更新完成,返回主线程,重新隐藏refresh panel,结束。

可以看到,如果不考虑刷新时间、状态文字等,实现"下拉刷新"实际上只需要做到2件事:

(1)隐藏refresh panel(初始时和刷新后)

隐藏refresh panel,即使其居于UITableView的上方且不可见,如下

 1 - (void)addDragHeaderView
2 {
3 if (self.shouldShowDragHeader && !dragHeaderView)
4 {
5 CGRect frame = CGRectMake(0, -self.dragHeaderHeight,
6 self.bounds.size.width, self.dragHeaderHeight);
7 dragHeaderView = [[Pull2RefreshView alloc]
8 initWithFrame:frame type:kPull2RefreshViewTypeHeader];
9 [self addSubview:dragHeaderView];
10 }
11 }

【 注意 :不应使用UITableView的tableHeaderView来作为refresh panel,一来会使得下拉刷新和自定义tableHeaderView无法共存,二来UITableView的内容视图是包含tableHeaderView的,即

tableView.contentSize.height == tableView.tableHeaderView.height

+ n * sectionHeaderView.height

+ m * cell.height

+ tableView.tableFooterView.height

因此想让tableHeaderView默认不可见,需要修改contentOffset的初始值并在用户滑动时控制滑动范围,非常麻烦。】

(2)显示refresh panel

当用户手指下拉达到预设值并离开屏幕,立即修改contentInset,使refresh panel保持显示,如下

1 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
2 {
3 if (scrollView.contentOffset.y < -self.dragHeaderHeight - 10.0f
4 {
5 //使refresh panel保持显示
6 self.contentInset = UIEdgeInsetsMake(self.dragHeaderHeight, 0, 0, 0);
7 }
8 }

如此,更新数据后再次隐藏refresh panel的方式也很明了

1 self.contentInset = UIEdgeInsetsZero;

2、动画、动态文字和刷新时间

一个标准的refresh panel(如新浪微博的),包含指示箭头、加载菊花、状态文字和更新时间四部分,如下

 1 @implementation Pull2RefreshView
2 {
3 UILabel *hintLabel;
4 UILabel *timeLabel;
5
6 UIImageView *arrowImageView;
7 UIActivityIndicatorView *indicatorView;
8
9 Pull2RefreshViewType refreshType;
10 }

在1中已经做到了refresh panel的显示的隐藏,2中只需要在合适的时候改变refresh panel的显示内容即可。

(1)"下拉可以刷新"—>"松开立即更新"

在UIScrollView的委托函数scrollViewDidScroll:中检测用户下拉的程度,达到预设值后就改变状态,如下:

 1 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
2 {
3 //拉动足够距离,状态变更为“松开....”
4 if (self.shouldShowDragHeader && dragHeaderView)
5 {
6 if (dragHeaderView.state == kPull2RefreshViewStateDragToRefresh
7 && scrollView.contentOffset.y < -self.dragHeaderHeight - 10.f
8 && !headerRefreshing
9 && !footerRefreshing)
10 {
11 [dragHeaderView flipImageAnimated:YES];
12 [dragHeaderView setState:kPull2RefreshViewStateLooseToRefresh];
13 }
14 }
15 }

修改指示箭头方向为向上,在setState中修改状态文本为"松开立即刷新"。

(2)"松开立即刷新"—>"加载中..." 
在UIScrollView的委托函数scrollViewDidEndDragging: willDecelerate:中检测用户手指离开屏幕时的情况:

 1 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
2 {
3 //拉动足够距离,松开后,状态变更为“加载中...”
4 if (dragHeaderView.state == kPull2RefreshViewStateLooseToRefresh
5 && scrollView.contentOffset.y < -self.dragHeaderHeight - 10.0f)
6 {
7 //使refresh panel保持显示
8 self.contentInset = UIEdgeInsetsMake(self.dragHeaderHeight, 0, 0, 0);
9 [dragHeaderView flipImageAnimated:YES];
10 [dragHeaderView setState:kPull2RefreshViewStateRefreshing];
11 }
12 }

相比1中添加了修改指示箭头方向,在setState中修改状态文本为"加载中..."。

(3)"加载中..."—>"下拉可以刷新" 
这一步需要由外部(通常是ViewController)判断何时执行,提供一个方法供外部调用,如下:

 1 - (void)completeDragRefresh
2 {
3 [UIView beginAnimations:nil context:NULL];
4 [UIView setAnimationDuration:0.3f];
5 self.contentInset = UIEdgeInsetsZero;
6 [UIView commitAnimations];
7
8 [dragView flipImageAnimated:NO];
9 [dragView setState:kPull2RefreshViewStateDragToRefresh];
10 }

指示箭头方向和状态文本恢复为初始状态,更新时间变为当前时间。

3、其他 
(1)"下拉刷新"和"上拉加载更多"的不同 
"下拉刷新"的refresh panel的位置始终不变,而"上拉加载更多"的refresh panel则需要随着tableView.contentSize的变化而变化。一个比较简单的方案是:

1 tableView.tableFooterView = dragFooterView;

在某些第三方实现中便是如此处理的,好处是简单到只需要一行代码,坏处是tableFooterView被占用了。考虑到tableFooterView在"上拉加载更多"的情境下不太需要自定义,影响不大。

另一个方案是在初始化时和数据更新后,设置refresh panel的frame使其始终保持正确位置。

此外,在触发刷新的条件上,二者也是不同的。"下拉刷新"时,为防止刷新"太过灵敏",需要设置一个阀值来控制,所以才有"松开立即刷新"。而"上拉加载更多"是在用户往下不断浏览内容的过程中触发的,因此只需滑动到内容底部就立即触发加载。

UITableView作为派生类,是和基类UIScrollView共享一个delegate属性的,即UITableViewDeleagte和UIScrollViewDelegate是同时指定的。这带来的问题是,想要封装一个支持"下拉刷新"和"上拉加载更多"的UITableView子类,恐怕不得不增加一层委托,将UITableViewDelegate中的各种方法都转到外部进行实现,如

1 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
2 {
3 if (self.pullDelegate && [self.pullDelegate respondsToSelector:@selector(pull2RefreshTableView:didSelectRowAtIndexPath:)])
4 {
5 [self.pullDelegate pull2RefreshTableView:self didSelectRowAtIndexPath:indexPath];
6 }
7 }

可谓麻烦至极。暂未想到较好的解决方案。【Mark】

4、封装的Pull2RefreshTableView Demo工程

使用iOS 6.1 SDK编译,使用ARC。

地址: https://github.com/cDigger/CDPullToRefreshDemo

1、Scroll View Programming Guide for iOS

https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/UIScrollView_pg/CreatingBasicScrollViews/CreatingBasicScrollViews.html

https://github.com/samvermette/SVPullToRefresh

 
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    //    scrollView == self.tableView == self.view
    // 如果tableView还没有数据,就直接返回
    if (self.statuses.count == 0 || self.tableView.tableFooterView.isHidden == NOreturn;
     
    CGFloat offsetY = scrollView.contentOffset.y;
     
    // 当最后一个cell完全显示在眼前时,contentOffset的y值
    CGFloat judgeOffsetY = scrollView.contentSize.height + scrollView.contentInset.bottom - scrollView.height - self.tableView.tableFooterView.height;
    if (offsetY >= judgeOffsetY) { // 最后一个cell完全进入视野范围内
        // 显示footer
        self.tableView.tableFooterView.hidden = NO;
         
        // 加载更多的微博数据
        [self loadMoreStatus];
    }
}

ios 上下拉刷新的更多相关文章

  1. iOS下拉刷新和上拉刷新

    在iOS开发中,我们经常要用到下拉刷新和上拉刷新来加载新的数据,当前这也适合分页.iOS原生就带有该方法,下面就iOS自带的下拉刷新方法来简单操作. 上拉刷新 1.在TableView里,一打开软件, ...

  2. 高仿IOS下拉刷新的粘虫效果

    最近看需要做一款下拉刷新的效果,由于需要和Ios界面保持一致,所以这用安卓的方式实现了ios下的下拉刷新的粘虫效果. 最新的安卓手机版本的QQ也有这种类似的效果,就是拖动未读信息的那个红色圆圈,拖动近 ...

  3. iOS 下拉刷新-上拉加载原理

    前言 讲下拉刷新及上拉加载之前先给大家解释UIScrollView的几个属性 contentSize是UIScrollView可以滚动的区域. contentOfinset 苹果官方文档的解释是&qu ...

  4. iOS 下拉刷新和加载更多 (OC\Swift)

    Swift语言出来之后, 可能还没有第三方的下拉刷新和上提加载, 所以自己用UIRefreshControl控件和UITableView实例的tableFooterView(底部视图)属性结合起来写了 ...

  5. iOS 下拉刷新 上拉加载实现原理

    1.下拉刷新 实现原理 if (scrollView.contentOffset.y < -100) { [UIView animateWithDuration:1.0 animations:^ ...

  6. iOS:下拉刷新控件UIRefreshControl的详解

    下拉刷新控件:UIRefreshControl 1.具体类信息: @interface UIRefreshControl : UIControl //继承控制类 - (instancetype)ini ...

  7. ios 下拉刷新开源框架 MJRefresh

    gitHub 下载框架 搜索MJExampleViewController.h 下拉刷新 MJTableViewController 上拉刷新 MJTableViewController Collec ...

  8. 仿IOS中下拉刷新的“雨滴”效果

    在IOS中,有非常赞的"水滴"下拉效果.非常久之前也想在Android上实现,可是苦于能力有限,一直未能付诸行动.这几天趁着空隙时间.写了一版初步实现,基本达到了"水滴& ...

  9. iOS 上拉刷新和下拉加在更多(第三方框架EGOTableViewPullRefresh)

    #import <UIKit/UIKit.h> @interface AppDelegate : UIResponder <UIApplicationDelegate> @pr ...

随机推荐

  1. 解决升级WordPress及插件需输入FTP账号的问题

    当添加,删除,升级 WordPress 插件或者直接升级 WordPress 的时候,WordPress 总是提示让你输入 FTP 帐号信息,非些烦人. 我们可以在 wp-config.php 中定义 ...

  2. iOS开发——UI进阶篇(九)block的巧用

    前面有提到通知.代理.kvo等方法来协助不同对象之间的消息通信,今天再介绍一下用block来解决这个问题 接着前面的例子 这里将功能在复述一遍 我把用block和通知放在一起比较,当然代理和kvo如何 ...

  3. 进军swift

    swift中文文档网站 http://letsswift.com/category/swiftguide/language-guide/ Swift的优缺点 , 来自珍妮讲解~~ 优点1.简洁(不是说 ...

  4. metasploit连接数据库

    表示一直出问题.不造哪里出错.望有知情人与本人取得联系! test

  5. Github如何删除repository(仓库)

    首先就是你的Github主页了. 第二步点击进入一个repository(仓库) 第三步点击右上的setting 将此页面滑动到最下面找个这个 点击删除即可!

  6. 简单的哈希表实现 C语言

    简单的哈希表实现 简单的哈希表实现 原理 哈希表和节点数据结构的定义 初始化和释放哈希表 哈希散列算法 辅助函数strDup 哈希表的插入和修改 哈希表中查找 哈希表元素的移除 哈希表打印 测试一下 ...

  7. 5.5---整数A转成整数B(CC150)

    自己的: public static int calcCost(int A,int B){ int ans = 1; int temp = A ^ B; while(temp != 1){ if(te ...

  8. 3.6---双栈排序(CC150)

    答,课本上的方法比较好. public static Stack<Integer> sort(Stack<Integer> s) { Stack<Integer> ...

  9. css position属性

    absolute, 屏幕为参照, 但固定在页面上,随页面滚动而动. fixed, 父元素没有指定position属性(或position属性为static时)==>屏幕为参照,固定在屏幕的某个位 ...

  10. VQuery选择器

    VQUery elements属性,储存选中的元素 参数 typeof的作用 字符串 class  ID tagName 函数 事件绑定 对象 直接插入 $函数 绑定事件 click方法 显示隐藏,- ...