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 ...
随机推荐
- 14-python登入教务网(python+bs4)
用request先得到到session对象,用其去放送请求,会自动保存cookie. 模拟有验证码的登入步骤: 1.发送请求登入页面: 2.分析验证码的地址,以及要将登入请求发往的地址(可以先输入错的 ...
- ubuntu14.04 64 位 vmware tools 问题2
当提示说open-vm-tools版本太低时可以这样解决 0.使用最新版本12.5的vmware player. 1.sudo apt-get autoremove open-vm-dkms open ...
- 王子和公主 UVa10635
[题目描述]:王子和公主 一个王子和公主在n*n的格子中行走,这些格子是有1....n^2的编号的.现在给定p+1个数,再给定q+1个数,公主和王子可以选择其中某些格子行走,求他们最多能走几个相同的格 ...
- python爬虫之Scrapy 使用代理配置——乾颐堂
在爬取网站内容的时候,最常遇到的问题是:网站对IP有限制,会有防抓取功能,最好的办法就是IP轮换抓取(加代理) 下面来说一下Scrapy如何配置代理,进行抓取 1.在Scrapy工程下新建“middl ...
- [SoapUI] 按照 Test Step Type 获取所有满足条件的 Test Step
获取当前测试用例下所有Groovy Script类型的测试步骤 def testStepList = testRunner.testCase.getTestStepsOfType(com.eviwar ...
- cookie与session组件
会话跟跟踪技术 cookie介绍 Djanjo中操作Cookle Session Django中Session相关方法 Django中的Session配置 CBV中加装饰器 session中运用aja ...
- 【转载】java实现rabbitmq消息的发送接受
原文地址:http://blog.csdn.net/sdyy321/article/details/9241445 本文不介绍amqp和rabbitmq相关知识,请自行网上查阅 本文是基于spring ...
- vscode填坑之旅: vscode设置中文,设置中文不成功问题
刚安装好的vscode界面显示中文,如何设置中文呢? 在locale.json界面设置”locale":"zh-cn"也未能实现界面为中文,在网上找了参考了,以下教程真实 ...
- C#记录程序运行时间
主要:using System.Diagnostics;当中有Stopwatch类: 介绍如下: // 摘要: // 提供一组方法和属性,可用于准确地测量运行时间. public class Stop ...
- python之CSV文件格式
1.csv文件是以一些以逗号分隔的值 import csv filename = "wenjian.csv" with open(filename) as f: reader = ...