• ios 上下拉刷新


     

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

    【转载请注明出处】

    本文将说明让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];
        }
    }
  • 相关阅读:
    如何创建一个WebService
    javascript调用WebService Hello World
    音频处理介绍(Linux手机)
    Android开机画面大整容
    android 源代码结构
    移植 android, touch screen 不能正常工作的问题
    为 Linux 应用程序编写 DLL
    6410 声卡wm9713 驱动分析
    android bootload源码网址
    fprintf 控制台代码,可以控制光标等,控制台显示时间源码
  • 原文地址:https://www.cnblogs.com/piaojin/p/5442020.html
Copyright © 2020-2023  润新知