UITableView介绍 之下拉刷新原理

UITableView下拉刷新原理

  我们在用tableView加载数据时,经常会用到下拉刷新这个功能,那么下拉刷新的原理是什么,如何个封装一个好用下拉刷新控件呢?下面由我来详细介绍一下。

下拉刷新

  下拉和上拉基本原理相似但是上拉刷新稍微复杂一点,所以我们先从下拉刷新讲起。

基本原理

  下拉刷新的基本原理是通过判断tableView的contenOffset的属性变化来做一些相应的处理,实现方式主要用到了状态机模式,下拉过程中主要有三种状态(正常状态、正在下拉、正在刷新)在这三种状态下做不同的处理。

  为了使用方便,所以代码的基本都封装在自定义控件中了,不说废话上代码

//
//  XQRefresh.h
//  下拉刷新
//
//  Created by code_xq on 16/3/5.
//  Copyright © 2016年 code_xq. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "UIView+Expand.h"

#ifndef XQRefresh
#define XQRefresh
    typedef NS_ENUM(NSInteger, RefreshState) {
        RefreshStateNormal = 0,
        RefreshStatePulling = 1,
        RefreshStateRefreshing = 2,
        RefreshStateDefault = 3
    };
#endif // XQRefresh

@interface XQRefreshHeader : UIView

+ (instancetype)initWithBlock:(void (^)(void))refreshingBlock;
- (void)beginRefreshing;
- (void)endRefreshing;

@end

  这里提供了三个方法所以调用方式也非常简单

    __weak typeof (self)weakSelf = self;
    XQRefreshHeader *refreshHeader = [XQRefreshHeader initWithBlock:^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
        dispatch_get_main_queue(), ^{
            
            // 业务逻辑
            ....
            [weakSelf.tableView reloadData];
            [weakSelf.refreshHeader endRefreshing];
        });
    }];
    [tableView addSubview:refreshHeader];

  XQRefreshHeader 的实现会用到ios的kvo机制,用来监听tableView的contentOffset的变化,这样做的好处是不用使用scrollView的众多代理方面,少了一层ViewController可以将所有的操作封装到view中实现,这里借鉴了MJRefresh的思路

/**
 * 当view被添加到父视图时被调用,父视图销毁时也会被调用此时newSuperview为空
 */
- (void)willMoveToSuperview:(UIView *)newSuperview {
    [super willMoveToSuperview:newSuperview];
    // 移除监听
    [self.superview removeObserver:self forKeyPath:XQRefreshContentOffset];
    
    if (newSuperview) {
        self.tableView = (UITableView *)newSuperview;
        
        self.width = newSuperview.width;
        self.height = newSuperview.height;
        self.bottom = newSuperview.top;
        
        UILabel *textLabel = [[UILabel alloc] init];
        [self addSubview:textLabel];
        textLabel.width = 100;
        textLabel.center = self.center;
        textLabel.height = 30;
        textLabel.bottom = self.height - 10;
        textLabel.textColor = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0];
        textLabel.textAlignment = NSTextAlignmentCenter;
        self.textLabel = textLabel;
        
        UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"arrow"]];
        [self addSubview:imageView];
        imageView.width = 18;
        imageView.height = 26;
        imageView.right = textLabel.left -5;
        imageView.bottom = self.height - 12;
        imageView.hidden = YES;
        self.imageView = imageView;
        
        
        UIActivityIndicatorView *activity = [[UIActivityIndicatorView alloc] init];
        activity.width = 50;
        activity.height = 50;
        activity.center = textLabel.center;
        
        [activity setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray];
        [self addSubview:activity];
        self.activity = activity;
        // 设置view的背景色
        self.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:0.9];
        self.hidden = YES;
        self.curState = RefreshStateDefault;
        // 添加监听
        [newSuperview addObserver:self forKeyPath:XQRefreshContentOffset options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];

    }
    
}

  这里用willMoveToSuperview方法初始化控件主要考虑到了这个方法的一个特性,当一个view被添加到父view时newSuperView不为空但是self.superView却为空,当控制器跳转时还会调用一次这个方法,此时正好相反newSuperView为空self.superView不为空,利用这个特性可以用来添加监听和移除监听,如果说只给某个对象的属性添加了kvo监听不去移除监听的话程序会报错。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    // 这里是为了记录初始化完成后的contentInset值
    if (!self.tableView.isTracking && self.curState == RefreshStateDefault) {
        _startInsetTop = self.tableView.contentInset.top;
        return;
    }
    
    if ([keyPath isEqualToString:XQRefreshContentOffset]) {
        CGFloat offsetY = - [change[@"new"] CGPointValue].y;
        CGFloat cValue = offsetY - _startInsetTop;
        
        if (cValue > 0 && cValue < refreshHeigh) {
            // 下拉过程但是没有超过给定的高度此时的状态为RefreshStatePulling 
            
        } else if (cValue >= refreshHeigh && !_tableView.isDragging) {
            // 正在刷新状态此时变化值等于给定的高度且手指离开屏幕 RefreshStateRefreshing 
        } else if (cValue <= 0){
            // 正常状态RefreshStateNormal
        } else if (cValue >= refreshHeigh && _tableView.isDragging) {
            // 下拉过程但是超过给定的高度此时的状态为RefreshStatePulling
        }
    } 
}

当contentOffset值发生改变时会调用上面的方法,状态方法如下

- (void)setStates:(RefreshState)state offsetValue:(CGFloat)offsetValue {
    switch (state) {
        case RefreshStateNormal: {
            // 清理工作将view中的所有改变了的属性恢复到下拉之前

        }
            break;
        case RefreshStatePulling: {

        }
            break;
        case RefreshStateRefreshing: {
            ....
            // 改变tableView的contentInset值,让它停留在下拉状态(重要)
            [UIView animateWithDuration:0.5 animations:^{
                self.tableView.contentInset = UIEdgeInsetsMake(offsetValue + refreshHeigh, 0, 0, 0);
            }];
            // 回调block(重要)
            _refreshingBolck();
        }
            break;
        default:
            break;
    }
    // 记录当前的刷新状态
    _curState = state; 
}

这里还要说明的一个细节是- (void)beginRefreshing方法的实现

- (void)beginRefreshing {
    self.hidden = NO;
    self.textLabel.text = self.textLabel.text = @"松手刷新...";
    [UIView animateWithDuration:0.09 animations:^{
    } completion:^(BOOL finished) {
        self.curState = RefreshStatePulling;
        [self setStates:RefreshStateRefreshing offsetValue:_startInsetTop];
    }]; 
}

因为此方法一般在tableView创建以后立即调用,此时有可能取到的startInsetTop原始值不正确,所有这里采用适当的延迟等tableView显示完成后再取初始值。

运行效果

上拉刷新

上拉刷新的原理和下拉相同,就是一些细节需要注意:

  • 每次刷新时刷新控件footerRefresh的y值要随contentSize的改变而改变
  • 下拉完成时也要改变footerRefresh的y值

完整代码的下载地址

转载于:https://www.cnblogs.com/code-xq/p/5263781.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值