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 ...
随机推荐
- .net 多线程同步的相关知识点
在多线程开发中,共享对象的同步是经常遇到的问题,以下总结了C#中线程同步的几种技术: 1,InterLocked原子操作 Decrement(ref int location);递减1 Add(ref ...
- DEPENDS工具和DUMPBIN工具使用
在系统部署运行时我们经常发现某个程序在开发机器中可以运行,但是部署在某台PC上缺不能运行,也存在在某些机器上可运行换一台机器却不能运行.主要表现出两种现象: (1).运行.调试时出现程序 ...
- JavaScript中的shift()、unshift()和pop()函数
JavaScript中的shift()和pop()函数 1.shift()函数 定义 该函数从从数组中删除第一项,并返回该删除项. 用法示例 var fruits = ["Banana& ...
- Asp.net相关知识和经验的碎片化记录
1.解决IIS7.0下“HTTP 错误 404.15 - Not Found 请求筛选模块被配置为拒绝包含的查询字符串过长的请求”问题 方案1:在程序的web.config中system.web节点里 ...
- 事务(进程 ID 64)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。
访问频率比较高的app接口,在后台写的异常日志会偶尔出现以下错误. 事务(进程 ID 64)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品.请重新运行该事务 实所有的死锁最深层的原因就是一个 ...
- bt协议详解 基础篇(上)
bt协议详解 基础篇(上) 最近开发了一个免费教程的网站,产生了仔细了解bt协议的想法,所以写了这一篇文章,后续还会写一些关于搜索和索引的东西,都是在开发这个网站的过程中学习到的技术,敬请期待. 1 ...
- google/protobuf hello world
/(ㄒoㄒ)/~~ 官网被墙 1. github > Search > protobuf or protocol buffers 2.https://github.com/google/p ...
- 获取cpu频率的代码
taskset是linux自带的一个命令,可用来将进程绑定到指定CPU 相关的函数有: sched_setaffinity, CPU_CLR, CPU_ISSET, CPU_SET, CPU_ZERO ...
- .NET基础 (03)生成、部署和管理
生成.部署和管理1 如何生成强签名的程序集2 如何把程序集放入GAC中3 延迟签名及其作用4 程序集的版本分哪几部分 1 如何生成强签名的程序集在生成程序集时,CLR提供了两种可选类型:强签名程序集. ...
- 编写高质量代码改善C#程序的157个建议——建议148:不重复代码
建议148:不重复代码 如果发现重复的代码,则意味着我们需要整顿一下,在继续前进. 重复的代码让我们的软件行为不一致.举例来说,如果存在两处相同的加密代码.结果在某一天,我们发现加密代码有个小Bug, ...