一. UIScrollView 的分類app
//做爲入口async
#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 下拉頭部視圖ide
#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
三. 父類, 監聽下拉變化,觸發響應的方法, 由子類實現ui
#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 的位置this
#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