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:]; 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 下拉刷新/上拉加载的更多相关文章
- Android 下拉刷新上啦加载SmartRefreshLayout + RecyclerView
在弄android刷新的时候,可算是耗费了一番功夫,最后发觉有现成的控件,并且非常好用,这里记录一下. 原文是 https://blog.csdn.net/huangxin112/article/de ...
- SwipeRefreshLayout实现下拉刷新上滑加载
1. 效果图 2.RefreshLayout.java package myapplication.com.myapplication; import android.content.Context; ...
- 移动端下拉刷新上拉加载-mescroll.js插件
最近无意间看到有这么一个上拉刷新下拉加载的插件 -- mescroll.js,个人感觉挺好用的,官网地址是:http://www.mescroll.com 然后我就看了一下文档,简单的写了一个小dem ...
- react-native 自定义 下拉刷新 / 上拉加载更多 组件
1.封装 Scroller 组件 /** * 下拉刷新/上拉加载更多 组件(Scroller) */ import React, {Component} from 'react'; import { ...
- mui下拉刷新上拉加载
新外卖商家端主页订单大厅页面 使用mui双webview,实现下拉刷新上拉加载 主页面: order_index.html <!doctype html> <html> < ...
- 带你实现开发者头条APP(五)--RecyclerView下拉刷新上拉加载
title: 带你实现开发者头条APP(五)--RecyclerView下拉刷新上拉加载 tags: -RecyclerView,下拉刷新,上拉加载更多 grammar_cjkRuby: true - ...
- JS+CSS实现的下拉刷新/上拉加载插件
闲来无事,写了一个当下比较常见的下拉刷新/上拉加载的jquery插件,代码记录在这里,有兴趣将代码写成插件与npm包可以留言. 体验地址:http://owenliang.github.io/pull ...
- RecyclerView下拉刷新上拉加载(一)
listview下拉刷新上拉加载扩展(一) http://blog.csdn.net/baiyuliang2013/article/details/50252561 listview下拉刷新上拉加载扩 ...
- MaterialRefreshLayout+ListView 下拉刷新 上拉加载
效果图是这样的,有入侵式的,非入侵式的,带波浪效果的......就那几个属性,都给出来了,自己去试就行. 下拉刷新 上拉加载 关于下拉刷新-上拉加载的效果,有许许多多的实现方式,百度了一下竟然有几十种 ...
- react-native-page-listview使用方法(自定义FlatList/ListView下拉刷新,上拉加载更多,方便的实现分页)
react-native-page-listview 对ListView/FlatList的封装,可以很方便的分页加载网络数据,还支持自定义下拉刷新View和上拉加载更多的View.兼容高版本Flat ...
随机推荐
- Sql_server_2014创建数据库自动备份
Sql_server_2014创建数据库自动备份 程序员的基础教程:菜鸟程序员
- RTX Server SDK跨服务器如何调用
1. 确认安装RTX Server SDK在开发的机器上必须确认已经安装了RTX Server SDK,并且与RTX Server的版本要一致.该计算机后面我们简称SDK计算机. 2. 步骤2 ...
- VS2017在Release下编译错误C1001
在使用VS2017编译C程序时,Debug模式下编译链接执行都没有问题,但是一转到Release模式下就出现下列编译链接错误(IDE:VS2017 /VC++/MFC程序,目标平台x86+Win32位 ...
- Spring查询方法的注入 为查询的方法注入某个实例
//这里是客户端的代码 当调用CreatePersonDao这个抽象方法或者虚方法的时候由配置文件返回指定的实例 为查询的方法注入某个实例 start static void Main(string[ ...
- 用Spring实现文件上传(CommonsMultipartFile)!
2012-02-16 18:10:26| 分类: 计算机--JAVA EE-|字号 订阅 spring中的文件上传实际比较容易1.页面中<html> <body> & ...
- IE6 BUG及解决方案
1.IE6中奇数宽高的BUG 一个外部的相对定位div,内部一个绝对定位的div(right:0) 可是在IE6下查看,却变成了right:1px的效果了: 解决方案就是将外部相对定位的div宽度改成 ...
- bootstrap缩略图及警示框制作
缩略图在网站中最常用的地方就是产品列表页面,一行显示几张图片,有的在图片底下(左侧或右侧)带有标题.描述等信息.Bootstrap框架将这一部独立成一个模块组件.并通过“thumbnail”样式配合b ...
- ArcGIS中地图导出格式比较(转)
转自:http://blog.sina.com.cn/s/blog_6438c8360101eqfx.html 有人问过这样的问题,用于出挂图的地图格式应该怎么选择?熟悉ArcGIS的用户都知道, ...
- Hadoop有点难
从看<Hadoop权威指南>第一眼开始,我一直觉得Hadoop很难,很难.....看着这本书,我觉得好像是文言文,我是真的看不懂,我的一腔热血瞬间冷了下来!很幸运,但是也不幸运,我来到了一 ...
- vs下C# WinForm 解决方案里面生成的文件都是什么作用?干什么的?
Properties文件夹 定义你程序集的属性 项目属性文件夹 一般只有一个 AssemblyInfo.cs 类文件,用于保存程序集的信息,如名称,版本等,这些信息一般与项目属性面板中的数据对应,不需 ...