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 ...
随机推荐
- 从零开始安装hue(原创-转载注明出处)
hue安装需要从github上面下载源码,进行编译安装.github上面给出的安装教程很简单 然而实际上在安装的过程中遇到了无数个坑,下面开始真正意义上的从零开始安装hue. 安装环境: centOS ...
- static_cast, dynamic_cast, reinterpret_cast, const_cast区别比较
隐式转换(implicit conversion) ; int b; b=a; short是两字节,int是四字节,由short型转成int型是宽化转换(bit位数增多),编译器没有warning,如 ...
- linux环境下搭建osm_web服务器一(Postgresql配置及osm2pgsql原始数据导入):
Postgresql配置及osm2pgsql原始数据导入 2012年,Ubuntu 12.04LTS发布,又一个长效支持版,我们又该更新OpenStreetMap服务器了,这次,将详细在博客中记录配置 ...
- 1.spring.net Look-up Method 查找方法的注入(方法是抽象的需要spring.net注入)
.为什么需要查找方法的注入 当Object依赖另一个生命周期不同的Object,尤其是当singleton依赖一个non-singleton时,常会遇到不少问题,Lookup Method Injec ...
- 从零开始学习前端JAVASCRIPT — 8、JavaScript基础COOKIE
1:通信协议 定义:通信协议是指双方实体完成通信或服务所必须遵循的规则和约定.协议定义了数据单元使用的格式,信息单元应该包含的信息与含义,连接方式,信息发送和接收的时序,从而确保网络中数据顺利地传送到 ...
- Arch Linux 使用markdown
Arch Linux 使用markdown pandoc 文档格式转换 pygments 代码高亮 markdown-mode.el 配置emacs pandoc 号称文件格式转换的瑞士军刀,这里主要 ...
- Python Selenium 之常用API
Selenium WebDriver下提供许多用来与浏览器.元素.鼠标.键盘.弹框.下拉菜单和列表的交互和设置方法.这些是计算机模拟人工进行自动化测试所必要依赖的方法.下面将用列表的方式总结出常用的A ...
- Ubuntu下安装配置java及环境变量
这里的办法不是在线安装,因为需要更新源(你懂的,费时费事~),so这里介绍在Ubuntu上手动下载安装配置Java环境变量 *系统:Ubuntu 16.4 1.下载jdk,直接用系统的Firefox浏 ...
- pig(数据流语言和编译器)学习https://www.w3cschool.cn/apache_pig/apache_pig_execution.html
当我们配置了hadoop集群执行的时候 可以通过命令ls进行查看 存储语法 STORE Relation_name INTO ' required_directory_path ' [USING fu ...
- 基于JDBC的数据库连接池技术研究与应用
引言 近年来,随着Internet/Intranet建网技术的飞速发展和在世界范围内的迅速普及,计算机 应用程序已从传统的桌面应用转到Web应用.基于B/S(Browser/Server)架构的3层开 ...