博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
UITableView介绍 之下拉刷新原理
阅读量:7165 次
发布时间:2019-06-29

本文共 5834 字,大约阅读时间需要 19 分钟。

UITableView下拉刷新原理

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

下拉刷新

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

基本原理

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

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

////  XQRefresh.h//  下拉刷新////  Created by code_xq on 16/3/5.//  Copyright © 2016年 code_xq. All rights reserved.//#import 
#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

你可能感兴趣的文章