tableView header Refresh 下拉刷新/上拉加载

一. UIScrollView 的分类

//作为入口

#import <UIKit/UIKit.h>
#import "RefreshHeader.h"
#import "RefreshFooter.h"

@interface UIScrollView (RefreshControl)<UIScrollViewDelegate>

@property (nonatomic,strong)RefreshHeader *header;
@property (nonatomic,strong)RefreshFooter *footer;
@end

#import "UIScrollView+RefreshControl.h"
#import <objc/runtime.h>

@implementation UIScrollView (RefreshControl)

- (void)setHeader:(RefreshHeader *)header
{
    header.backgroundColor = [UIColor redColor];
    [self insertSubview:header atIndex:0];
    
    objc_setAssociatedObject(self, @selector(header), header, OBJC_ASSOCIATION_ASSIGN);
}

- (RefreshHeader *)header
{
    return objc_getAssociatedObject(self, @selector(header));
}

- (void)setFooter:(RefreshFooter *)footer
{
    footer.backgroundColor = [UIColor redColor];

    [self insertSubview:footer atIndex:0];
    objc_setAssociatedObject(self, @selector(footer), footer, OBJC_ASSOCIATION_ASSIGN);
}

- (RefreshFooter *)footer
{
    return objc_getAssociatedObject(self, @selector(footer));
}

@end

二.RefreshHeader 下拉头部视图

#import <UIKit/UIKit.h>
#import "RefreshControlElement.h"

@interface RefreshHeader : RefreshControlElement
+ (RefreshHeader *)headerWithNextStep:(void(^)())next;
+ (RefreshHeader *)headerWithTarget:(id)target nextAction:(SEL)action;
@end

#import "RefreshHeader.h"

@implementation RefreshHeader

+ (RefreshHeader *)headerWithNextStep:(void(^)())next
{
    RefreshHeader *header = [[self alloc]init];
    header.headerHandle = next;
    return header;
}

+ (RefreshHeader *)headerWithTarget:(id)target nextAction:(SEL)action
{
    RefreshHeader *header = [[self alloc]init];
    header.refreshTarget = target;
    header.refreshAction = action;
    return header;
}

- (void)afterMoveToSuperview
{
    [super afterMoveToSuperview];
    self.frame = CGRectMake(0, -RefreshControlContentHeight, self.scrollView.frame.size.width, RefreshControlContentHeight);
}

- (void)refreshControlContentOffsetDidChange:(CGFloat)y isDragging:(BOOL)dragging
{
    if (y < -RefreshControlContentInset && y < 0)
    {
        [self refreshControlWillEnterRefreshState];//进入刷新状态, 旋转箭头
        if (!dragging) {
            [self refreshControlRefreshing];//正在刷新,展示菊花 active
        }
        return;
    }
    [self refreshControlWillQuitRefreshState];
}

- (void)refreshControlRefreshing
{
    [super refreshControlRefreshing];
    
    //刷新中,使顶部便宜 contentInset
    [UIView animateWithDuration:RefreshControlTimeIntervalDuration animations:^{
        self.scrollView.contentInset = UIEdgeInsetsMake(RefreshControlContentInset, 0, 0, 0);
    }];
    //隐藏箭头
    self.arrow.hidden = YES;
}

@end

三. 父类, 监听下拉变化,触发响应的方法, 由子类实现

#import <UIKit/UIKit.h>

#define RefreshMsgSend(...) ((void (*)(void *, SEL, UIView *))objc_msgSend)(__VA_ARGS__)
#define RefreshMsgTarget(target) (__bridge void *)(target)

extern const CGFloat RefreshControlContentHeight;
extern const CGFloat RefreshControlContentInset;
extern const CGFloat RefreshControlAnimationDuration;
extern const CGFloat RefreshControlArrowImageWidth;
extern const CGFloat RefreshControlTimeIntervalDuration;

typedef void (^NextStepHandle)();

typedef enum : NSUInteger {
    
    RefreshControlStateWillBeRefeshing,
    RefreshControlStateRefreshing,
    RefreshControlStateWillBeFree,
    RefreshControlStateFree
} RefreshState;

@interface RefreshControlElement : UIView
@property (nonatomic,weak)   UIScrollView *scrollView;
@property (nonatomic,strong) UIImageView *arrow;
@property (nonatomic,strong) UIActivityIndicatorView *activity;

@property (nonatomic,assign)BOOL isRefreshing;

@property (nonatomic,copy)NextStepHandle headerHandle;
@property (nonatomic,copy)NextStepHandle footerHandle;

@property (nonatomic,weak)id refreshTarget;
@property (nonatomic,assign)SEL refreshAction;

@property (nonatomic,assign)RefreshState refreshStyle;

- (void)refreshControlWillEnterRefreshState;//即将进入刷新状态
- (void)refreshControlRefreshing;//正在刷新
- (void)canRefreshAndNotDragging;//松手并达到刷新状态
- (void)refreshControlWillQuitRefreshState;//不满足刷新状态/退出刷新状态


/**
 由子类实现
 */
- (void)refreshControlContentOffsetDidChange:(CGFloat)y isDragging:(BOOL)dragging;
- (void)refreshControlContentSizeDidChange:(CGFloat)height;

- (void)endRefresh;

- (void)afterMoveToSuperview;
@end


#import "RefreshControlElement.h"
#import "RefreshControlConst.h"
#import <objc/message.h>

const CGFloat RefreshControlContentHeight       = 40;
const CGFloat RefreshControlContentInset        = 80;
const CGFloat RefreshControlArrowImageWidth            = 15;
const CGFloat RefreshControlAnimationDuration          = 0.3f;
const CGFloat RefreshControlTimeIntervalDuration       = 0.1f;


@implementation RefreshControlElement

- (void)willMoveToSuperview:(UIView *)newSuperview
{
    [super willMoveToSuperview:newSuperview];
    
    if ([newSuperview isKindOfClass:[UICollectionView class]]) {
        ((UICollectionView *)newSuperview).alwaysBounceVertical = YES;
    }
    self.scrollView = (UIScrollView *)newSuperview;
    [self removeObservers];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [self afterMoveToSuperview];
    });
    [self addObservers];
}


- (void)afterMoveToSuperview
{
    _arrow = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"arrow"]];
    
    _arrow.backgroundColor = [UIColor greenColor];
#warning console will input 'error Two-stage rotation animation is deprecated' when rotate arrow. Because this application should use the smoother single-stage animation.that I was simply using the Tab Bar Controller wrong: the tab bar should only be used as a root controller, however I inserted a navigation controller before it.
    _arrow.frame = CGRectMake((CGRectGetWidth(self.scrollView.frame)-RefreshControlArrowImageWidth)/2, 0, RefreshControlArrowImageWidth, RefreshControlContentHeight);
    [self addSubview:_arrow];
}

- (UIActivityIndicatorView *)activity
{
    if (!_activity) {
        _activity = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
        _activity.frame = self.arrow.frame;
        [_activity setHidesWhenStopped:YES];
        [self addSubview:_activity];
    }
    return _activity;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    if (!self.isUserInteractionEnabled||self.hidden) return;
    //一直观察着 contentOffset的变化, 从而触发响应的方法
    if ([keyPath isEqualToString:RefreshControlObserverKeyPathContentOffset]) {
        [self refreshControlContentOffsetDidChange:([change[@"new"] CGPointValue].y) isDragging:self.scrollView.isDragging];
    }
    if ([keyPath isEqualToString:RefreshControlObserverKeyPathContentSize]) {
        [self refreshControlContentSizeDidChange:([change[@"new"] CGSizeValue].height)];
    }
}

- (void)endRefresh
{
    dispatch_async(dispatch_get_main_queue(), ^{
            [UIView animateWithDuration:0.2f animations:^{
                self.scrollView.contentInset = UIEdgeInsetsZero;
                [self.activity stopAnimating];
            }];
    });
    self.arrow.hidden = NO;
}

- (void)refreshControlWillEnterRefreshState
{
    [UIView animateWithDuration:RefreshControlAnimationDuration animations:^{
        self.arrow.transform = CGAffineTransformMakeRotation(M_PI);
    }];
}

- (void)refreshControlWillQuitRefreshState
{
    [UIView animateWithDuration:RefreshControlAnimationDuration animations:^{
        self.isRefreshing = NO;
        self.arrow.transform = CGAffineTransformMakeRotation(0);
    }];
}

- (void)addObservers
{
    [self.scrollView addObserver:self forKeyPath:RefreshControlObserverKeyPathContentOffset options:NSKeyValueObservingOptionNew context:nil];
    [self.scrollView addObserver:self forKeyPath:RefreshControlObserverKeyPathContentSize options:NSKeyValueObservingOptionNew context:nil];
}

- (void)removeObservers
{
    [self.superview removeObserver:self forKeyPath:RefreshControlObserverKeyPathContentSize];
    [self.superview removeObserver:self forKeyPath:RefreshControlObserverKeyPathContentOffset];
}


/**
 子类重写这些方法
 */
- (void)refreshControlContentOffsetDidChange:(CGFloat)y isDragging:(BOOL)dragging{}
- (void)refreshControlContentSizeDidChange:(CGFloat)height{}
//正在刷新
- (void)refreshControlRefreshing
{
    if (self.isRefreshing) {
        return;
    }
    self.isRefreshing = YES;

    //触发 刷新方法
    if (self.refreshAction && self.refreshTarget&&[self.refreshTarget respondsToSelector:self.refreshAction]){
        [self.refreshTarget performSelector:self.refreshAction];
        RefreshMsgSend(RefreshMsgTarget(self.refreshTarget), self.refreshAction, self);
    }
    else{
      if (self.headerHandle) self.headerHandle();
      if (self.footerHandle) self.footerHandle();
    }
    
    //转动菊花
    [self.activity startAnimating];
    
}
- (void)canRefreshAndNotDragging{}//松手并达到刷新状态

@end

//Footer , 需要计算 tableView 的内容高度, 从而设定 footer 的位置

#import <UIKit/UIKit.h>
#import "RefreshControlElement.h"

@interface RefreshFooter : RefreshControlElement
+ (RefreshFooter *)footerWithNextStep:(void(^)())next;
+ (RefreshFooter *)footerWithTarget:(id)target nextAction:(SEL)action;
@end
#import "RefreshFooter.h"

@interface RefreshFooter()
@end

@implementation RefreshFooter
{
    CGFloat superViewLastContentHeight;
}

+ (RefreshFooter *)footerWithNextStep:(void(^)())next
{
    RefreshFooter *footer = [[self alloc]init];
    footer.footerHandle = next;
    return footer;
}

+ (RefreshFooter *)footerWithTarget:(id)target nextAction:(SEL)action
{
    RefreshFooter *footer = [[self alloc]init];
    footer.refreshTarget = target;
    footer.refreshAction = action;
    return footer;
}

- (void)afterMoveToSuperview
{
    [super afterMoveToSuperview];
    //footer 需要根据scrollView的内容高度 contentSize来计算 footer 的位置
    self.frame = CGRectMake(0, self.scrollView.contentSize.height, self.scrollView.frame.size.width, RefreshControlContentHeight);
    self.arrow.transform = CGAffineTransformMakeRotation(M_PI);
}

- (void)refreshControlContentOffsetDidChange:(CGFloat)y isDragging:(BOOL)dragging
{
    dispatch_async(dispatch_get_main_queue(), ^{
        if (y >= self.scrollView.contentSize.height - self.scrollView.frame.size.height + RefreshControlContentInset&& y>RefreshControlContentInset)
        {
            [self refreshControlWillEnterRefreshState];
            if (!dragging) {
                [self refreshControlRefreshing];
            }
            return;
        }
        [self refreshControlWillQuitRefreshState];
    });
}

- (void)refreshControlContentSizeDidChange:(CGFloat)height
{
    if (superViewLastContentHeight == height) {
        return;
    }
    CGRect rect = self.frame;
    rect.origin.y = height;
    self.frame = rect;
    superViewLastContentHeight = height;
}

- (void)refreshControlWillQuitRefreshState
{
    [UIView animateWithDuration:RefreshControlAnimationDuration animations:^{
        self.isRefreshing = NO;
        self.arrow.transform = CGAffineTransformMakeRotation(M_PI);
    }];
}

- (void)refreshControlWillEnterRefreshState
{
   [UIView animateWithDuration:RefreshControlAnimationDuration animations:^{
       self.arrow.transform = CGAffineTransformMakeRotation(0);
   }];
}

- (void)refreshControlRefreshing
{
    [super refreshControlRefreshing];
    [UIView animateWithDuration:RefreshControlTimeIntervalDuration animations:^{
        self.scrollView.contentInset = UIEdgeInsetsMake(0, 0, RefreshControlContentInset, 0);
    }];
    self.arrow.hidden = YES;
}
@end
原文地址:https://www.cnblogs.com/daxueshan/p/8559145.html