• 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
  • 相关阅读:
    北漂IT男返乡2年的三线楼市观察(宜昌夷陵篇)-原创
    写一本书作者到底能拿到多少稿酬?
    书是如何定价的?
    一本书出版社拿多少,作者拿多少?书的成本几何?出版一本书出版社到底能赚多少钱?(转)
    微服务架构最强详解
    今日头条号短视频自媒体研究---最开始的短视频设备资金投入不要太大(原创)
    今日头条号短视频自媒体研究---新手上路,正确拍短视频并上传(原创)
    HTTP请求中的缓存(cache)机制
    linux kernel 中断子系统之(一)-- ARM GIC 硬件【转】
    嵌入式Linux——kmsg:分析/proc/kmsg文件以及写自己的/proc/mymsg【转】
  • 原文地址:https://www.cnblogs.com/daxueshan/p/8559145.html
Copyright © 2020-2023  润新知