一. 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:]; 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:];
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(, -RefreshControlContentHeight, self.scrollView.frame.size.width, RefreshControlContentHeight);
} - (void)refreshControlContentOffsetDidChange:(CGFloat)y isDragging:(BOOL)dragging
{
if (y < -RefreshControlContentInset && y < )
{
[self refreshControlWillEnterRefreshState];//进入刷新状态, 旋转箭头
if (!dragging) {
[self refreshControlRefreshing];//正在刷新,展示菊花 active
}
return;
}
[self refreshControlWillQuitRefreshState];
} - (void)refreshControlRefreshing
{
[super refreshControlRefreshing]; //刷新中,使顶部便宜 contentInset
[UIView animateWithDuration:RefreshControlTimeIntervalDuration animations:^{
self.scrollView.contentInset = UIEdgeInsetsMake(RefreshControlContentInset, , , );
}];
//隐藏箭头
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 = ;
const CGFloat RefreshControlContentInset = ;
const CGFloat RefreshControlArrowImageWidth = ;
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)/, , 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();
}];
} - (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(, 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();
}];
} - (void)refreshControlRefreshing
{
[super refreshControlRefreshing];
[UIView animateWithDuration:RefreshControlTimeIntervalDuration animations:^{
self.scrollView.contentInset = UIEdgeInsetsMake(, , RefreshControlContentInset, );
}];
self.arrow.hidden = YES;
}
@end

tableView header Refresh 下拉刷新/上拉加载的更多相关文章

  1. Android 下拉刷新上啦加载SmartRefreshLayout + RecyclerView

    在弄android刷新的时候,可算是耗费了一番功夫,最后发觉有现成的控件,并且非常好用,这里记录一下. 原文是 https://blog.csdn.net/huangxin112/article/de ...

  2. SwipeRefreshLayout实现下拉刷新上滑加载

    1. 效果图 2.RefreshLayout.java package myapplication.com.myapplication; import android.content.Context; ...

  3. 移动端下拉刷新上拉加载-mescroll.js插件

    最近无意间看到有这么一个上拉刷新下拉加载的插件 -- mescroll.js,个人感觉挺好用的,官网地址是:http://www.mescroll.com 然后我就看了一下文档,简单的写了一个小dem ...

  4. react-native 自定义 下拉刷新 / 上拉加载更多 组件

    1.封装 Scroller 组件 /** * 下拉刷新/上拉加载更多 组件(Scroller) */ import React, {Component} from 'react'; import { ...

  5. mui下拉刷新上拉加载

    新外卖商家端主页订单大厅页面 使用mui双webview,实现下拉刷新上拉加载 主页面: order_index.html <!doctype html> <html> < ...

  6. 带你实现开发者头条APP(五)--RecyclerView下拉刷新上拉加载

    title: 带你实现开发者头条APP(五)--RecyclerView下拉刷新上拉加载 tags: -RecyclerView,下拉刷新,上拉加载更多 grammar_cjkRuby: true - ...

  7. JS+CSS实现的下拉刷新/上拉加载插件

    闲来无事,写了一个当下比较常见的下拉刷新/上拉加载的jquery插件,代码记录在这里,有兴趣将代码写成插件与npm包可以留言. 体验地址:http://owenliang.github.io/pull ...

  8. RecyclerView下拉刷新上拉加载(一)

    listview下拉刷新上拉加载扩展(一) http://blog.csdn.net/baiyuliang2013/article/details/50252561 listview下拉刷新上拉加载扩 ...

  9. MaterialRefreshLayout+ListView 下拉刷新 上拉加载

    效果图是这样的,有入侵式的,非入侵式的,带波浪效果的......就那几个属性,都给出来了,自己去试就行. 下拉刷新 上拉加载 关于下拉刷新-上拉加载的效果,有许许多多的实现方式,百度了一下竟然有几十种 ...

  10. react-native-page-listview使用方法(自定义FlatList/ListView下拉刷新,上拉加载更多,方便的实现分页)

    react-native-page-listview 对ListView/FlatList的封装,可以很方便的分页加载网络数据,还支持自定义下拉刷新View和上拉加载更多的View.兼容高版本Flat ...

随机推荐

  1. spring4-4-jdbc-01

    1.建立数据属性文件db.properties jdbc.user=root jdbc.password=root jdbc.driverClass=com.mysql.jdbc.Driver jdb ...

  2. Maven详解【面试+工作】 各种安装 没用

    1 Maven介绍1.1 项目开发中遇到的问题 1.都是同样的代码,为什么在我的机器上可以编译执行,而在他的机器上就不行? 2.为什么在我的机器上可以正常打包,而配置管理员却打不出来? 3.项目组加入 ...

  3. [SoapUI] 比较两个不同环境下的XML Response, 从外部文件读取允许的偏差值,输出结果到Excel

    import static java.lang.Math.* import java.text.NumberFormat import java.awt.Color import com.eviwar ...

  4. jquery页面初始化控件时间

    this.InitControlTime = function () { if ($("#txt_Id_").val() == "") { var today ...

  5. Microsoft(C)注册服务器(32位)CPU占用高

    Microsoft(C)注册服务器(32位)CPU占用高 摘自:https://blog.csdn.net/jtsqrj/article/details/83034252 2018年10月12日 23 ...

  6. Golang 之 Qrcode 二维码

    二维码大行其道,尤其 qrcode ,怎么能少了大golang 呢. follow me . 1.引用 go get github.com/skip2/go-qrcode 2.写 package ma ...

  7. python 数据清洗

    前言 1. 删除重复 2. 异常值监测 3. 替换 4. 数据映射 5. 数值变量类型化 6. 创建哑变量 统计师的Python日记[第7天:数据清洗(1)] 前言 根据我的Python学习计划: N ...

  8. 编写高质量代码改善C#程序的157个建议——建议139:事件处理器命名采用组合方式

    建议139:事件处理器命名采用组合方式 所谓事件处理器,就是实际被委托执行的那个方法.查看如下代码: public MainWindow() { InitializeComponent(); Butt ...

  9. mysql转ElasticSearch的分析 及JAVA API 初探

    前言 最近工作中在进行一些技术优化,为了减少对数据库的压力,对于只读操作,在程序与db之间加了一层-ElasticSearch.具体实现是db与es通过bin-log进行同步,保证数据一致性,代码调用 ...

  10. K倍区间 蓝桥杯

    问题描述 给定一个长度为N的数列,A1, A2, ... AN,如果其中一段连续的子序列Ai, Ai+1, ... Aj(i <= j)之和是K的倍数,我们就称这个区间[i, j]是K倍区间. ...