一个类搞定UIScrollView那些事
前言
UIScrollView可以说是我们在日常编程中使用频率最多、扩展性最好的一个类,根据不同的需求和设计,我们都能玩出花来,当然有一些需求是大部分应用通用的,今天就聊一下以下需求,在一个category中统统搞定:
1下拉刷新:支持下拉过程中GIF逐帧,loading时可自定义帧率
2上拉更多:支持GIF,支持提前加载,滚动到最后能替换图片作为提示
3回到顶部:当滚动多屏之后,往回划时右下角弹出回到顶部按钮
4自定义一个按钮,在回到顶部按钮上面,可自定事件,并且会根据回到顶部按钮的出现或者消失上下移动,有动画过渡
此类适用于任何继承于scrollView的类
下拉刷新、上拉更多
在头文件中我们声明了2个bool的属性,在没有特殊图片要求的情况下,我们只需要对这2个bool进行操作就可以了,分别是 useRefreshHeader
和useLoadMoreFooter
,由于是category,我们需要用 ASSOCIATION
来为属性添加set
和get
方法,并且在set
方法中,我们为其创建一个默认的view,使用默认的文案和默认的gif图片,也就是上图中你们所看到的猫头,当然也可以自定义gif,并且指定其播放的速度,为此,我提供了以下几个接口方法:
//自定义gif下拉刷新 //传入图片名称
- (void)setRefreshProgressImageName:(NSString *)progressImageName //下滑时的图片
LoadingImageName:(NSString *)loadingImageName //加载时的图片
showTitles:(NSArray *)titles //显示的title
LoadingImageFrameRate:(NSInteger)frameRate; //加载动画的帧率 //传入图片对象
- (void)setRefreshProgressImage:(UIImage *)progressImage //下滑时的图片
LoadingImage:(UIImage *)loadingImage //加载时的图片
showTitles:(NSArray *)titles //显示的title
LoadingImageFrameRate:(NSInteger)frameRate; //加载动画的帧率 //自定义gif上拉更多 //传入图片名称
- (void)setLoadMoreProgressImageName:(NSString *)progressImageName //跟手动画
LoadingImageName:(NSString *)loadingImageName //加载动画
showTitles:(NSArray *)titles //显示的title
LoadingImageFrameRate:(NSInteger)frameRate; //加载动画的帧率 //传入图片对象
- (void)setLoadMoreProgressImage:(UIImage *)progressImage //跟手动画
LoadingImage:(UIImage *)loadingImage //加载动画
showTitles:(NSArray *)titles //显示的title
LoadingImageFrameRate:(NSInteger)frameRate; //加载动画的帧率
更为关键的,我们需要用KVO监听以下几个属性:
1.contentOffset
2.contentSize
3.frame
4.contentInset
属性监听
1.监听contentOffset
的目的
由于我们使用了category,所以无法通过scrollView的delegate
来获取其滚动,和一般的下拉刷新一样,我们在scrollView的头部添加了一个View,所以必须监听contentOffset
才能做出行为判断,上拉更多亦是如此,废话不多说,直接上代码:
if([keyPath isEqualToString:@"contentOffset"])
{
if (self.useRefreshHeader && self.refreshHeaderView && [self.refreshHeaderView isKindOfClass:[TMMuiPullView class]] && [self.refreshHeaderView respondsToSelector:@selector(scrollViewDidScroll:)])
{
[self.refreshHeaderView scrollViewDidScroll:[[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]];
} if (self.useLoadMoreFooter && self.loadMoreFooterView && [self.loadMoreFooterView isKindOfClass:[TMMuiPullView class]] && [self.loadMoreFooterView respondsToSelector:@selector(scrollViewDidScroll:)])
{
[self.loadMoreFooterView scrollViewDidScroll:[[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]];
}
}
我们通过contentOffset的变化模拟出了一个scrollViewDidScroll的方法,并且在refreshHeaderView
和loadMoreFooterView
中来监听此方法,而这2个view都是TMMuiPullView
,所以其实我只需要实现一次,我会在下文中来详细谈这个方法。
2.监听contentSize
和frame
的目的
由于useRefreshHeader
和useLoadMoreFooter
声明之后,无法避免需要改动scrollView的contentSize
和frame
的值,所以每当这2个值发生变化的时候,我们需要去调整这2个view的位置和布局
3.监听contentInset
的目的
ios 7之后,scrollView在一定条件下,系统会调整其contentInset
,或者人为的调整了contentInset
,为了能让scrollView在各种动作之后依然处于正确的位置上,我们必须监听这个值,并且储存起来。
TMMuiPullView 中的scrollViewDidScroll
方法
这个方法可谓是本类中最繁琐的方法,任何一个动作都需要区分是刷新还是更多2个情况来讨论
TMMuiPullViewTypeRefresh
1.当滚动的offset.y是大于0的时候,我们就直接return
,因为之后不可能触发下拉刷新的动作,也就没有必要继续往下走了;
2.我们假设拉到触发刷新动作的距离是100%的话,那么在未触发前都会有对应的一个进度,通过这个进度我们去gif中获取处于这 个进度的那一帧图片,并且把他显示到View上,从而达到跟手逐针播放的效果
3.接下来就是状态判断了,在此,我们为其定制了5个状态:
typedef NS_ENUM(NSUInteger, TMMuiPullState)
{
TMMuiPullStateNone = , //正常状态
TMMuiPullStateTriggering,
TMMuiPullStateTriggered,
TMMuiPullStateLoading,
TMMuiPullStateCanFinish
};
每一个状态都将是成为下一个状态的条件,这让我想到了“密室逃脱”
- 第一个房间–
TMMuiPullStateNone
这个房间里我们通过滚动到偏移量小于固定某个值的时候&手指依旧按着,那么就能拿到下一个门的钥匙。 - 第二个房间–
TMMuiPullStateTriggering
这个房间拿到钥匙的条件是:进度达到100%;手指依旧按着。 - 第三个房间–
TMMuiPullStateTriggered
我们房间的难度也是越来越苛刻的,这个房间需要满足3个条件:1.没有在loading,2.手指放开,3.放开手指的瞬间,进度是大于95%的。当满足这些条件的时候,播放loading动画,调用delegate方法,然后恭喜你,进入下一个房间 - 第四个房间–
TMMuiPullStateLoading
奇怪,这个房间竟然没有任何提示,也不用做任何操作,怎么回事?需要进入下一个房间的钥匙其实需要delegate塞进来,否则,你永远没办法进入下一个房间,所以在用的时候,我们需要在delegate方法调用loading,loading完成之后调用一个方法把新的房间钥匙送进来。 - 第五个房间
TMMuiPullStateCanFinish
这个最后一个房间,我们就等着进度恢复成初始状态,那么就能顺利的完成这一次密室逃脱,状态置为TMMuiPullStateNone
TMMuiPullViewTypeLoadMore
1.当hasMorePage等于yes的时候,我们用的是loading图片,而当hasMorePage等于no时,我们就用没有更多的图片。 2.其余部分其实和TMMuiPullViewTypeRefresh
是同一个道理,只是关键性的值发生了改变这里就不在重复展开了。
回到顶部
1.当设置ShowBackTopButton为Yes时,我们会为scrollView添加一个contentOffset的监听
2.创建一个回到顶部的button,并且添加到与整个scrollView的superView上,并且在屏幕下方隐藏。
3.contentOffset 偏移量大于2屏,且往回滚动时,回到顶部按钮向上做动画上升,否则动画下落
4.当点击回到顶部按钮时,scrollView就调用setContentOffset的方法,滚回顶部
滚回顶部上方的自定义按钮
1.在滚回顶部上方可以添加一个按钮,位于整个试图的右下角,当滚回顶部按钮出现时,它也会随之上升,相反会滚回原来的位置。
注意点
在这个类中我们需要有2个注意点:
1.KVO 不要多次添加,当多次添加KVO时就会有多个监听者监听同一个事件,所以我在每次添加监听的时候都会try着删除一次,记住,一定要写try,否则会crash。
2.runtime交换方法,因为我有许多指针一直持有内存,如果不把这个指针置为nil,也会导致crash,但是我们知道category是没办法重写方法的,所以我们只能用method_exchangeImplementations
的方法来做交换,这里我交换了2个地方一个是dealloc
,另一个是didMoveToSuperview
,因为我们有一些view是放在superView上面的。
总结
虽然本篇博客并没有提及任何实现的具体代码,而是提供一种思路,希望通过了解这些思路,能构建出一个属于你自己的scrollView的category,如果真的有需要,我会代码脱敏之后分享。
一个类搞定UIScrollView那些事的更多相关文章
- 一个类搞定UIScrollView那些事儿
前言 UIScrollView可以说是我们在日常编程中使用频率最多.扩展性最好的一个类,根据不同的需求和设计,我们都能玩出花来,当然有一些需求是大部分应用通用的,今天就聊一下以下需求,在一个categ ...
- 一个类搞定SQL条件映射解析,实现轻量简单实用ORM功能
个人觉得轻简级的ORM既要支持强类型编码,又要有执行效率,还要通俗易懂给开发者友好提示,结合Expression可轻松定制自己所需要功能. Orm成品开源项目地址https://github.com/ ...
- Jquery一个slideToggle搞定div的隐藏与显示
Jquery一个slideToggle搞定div的隐藏与显示 <!DOCTYPE html> <html> <head> <script src=" ...
- 将你的前端应用打包成docker镜像并部署到服务器?仅需一个脚本搞定
1.前言 前段时间,自己搞了个阿里云的服务器.想自己在上面折腾,但是不想因为自己瞎折腾而污染了现有的环境.毕竟,现在的阿里云已经没有免费的快照服务了.要想还原的话,最简单的办法就是重新装系统.而一旦重 ...
- iOS之下拉放大,上推缩小,一个方法搞定
先来看看效果吧. 讲讲大概的实现思路:1、创建头部的视图和tableview,需要注意的是tableview要设置contentInset,contentInsent 的顶部要和头部视图的背景图的高度 ...
- 一个注解搞定SpringBoot接口定制属性加解密
前言 上个月公司另一个团队做的新项目上线后大体上运行稳定,但包括研发负责人在内的两个人在项目上线后立马就跳槽了,然后又交接给了我这个「垃圾回收人员」. 本周甲方另一个厂家的监控平台扫描到我们这个项目某 ...
- 如何让两个div在同一行显示?一个float搞定
最近在学习div和css,遇到了一些问题也解决了很多以前以为很难搞定的问题.比如:如何让两个div显示在同一行呢?(不是用table表格,table对SE不太友好)其实,<div> 是一个 ...
- 收不到Win10正式版预订通知?一个批处理搞定
目前,已经有不少Win7.Win8.1用户在系统右下角收到Win10正式版的预订提示窗口.点击接受预订后,系统会将Win10正式版所需的安装文件提前下载好,7月29日正式发布的时候,就可以第一时间升级 ...
- H5 拖拽,一个函数搞定,直接指定对象设置可拖拽
页面上,弹个小窗体,想让它可以拖拽,又不想 加载一堆js,就简单的能让他可以拖动? 嗯,下面有这样一个函数,调用下就好了! 1. 先来说说 H5的 拖拽 在 HTML5 中,拖放是标准的一部分,任何元 ...
随机推荐
- 在游戏中使用keybd_event的问题
转自在游戏中使用keybd_event的问题 今天发现在游戏中,keybd_event不能使用,结果发现游戏是使用directinput实现读取键盘的,关键还是扫描码的问题,我抄了一段老外的代码,经测 ...
- Intellij IDEA快速编写代码经验总结
sout=System.out.println(); soutp=System.out.println(""); soutv=System.out.println("变量 ...
- js常用操作代码
页面前进后退<input type=button value=刷新 onclick="window.location.reload()"><input type= ...
- minicom-2.4安装配置
minicom-2.4安装说明 1.#tar –zxvf minicom-2.4.tar.gz 解压开有连个文件,minicom-2[1].4.tar.gz 和minirc.dfl rpm包方式# ...
- 【转】Android开发之旅:环境搭建及HelloWorld
原文网址: http://www.cnblogs.com/skynet/archive/2010/04/12/1709892.html 引言 本系列适合0基础的人员,因为我就是从0开始的,此系列记录我 ...
- 国外成熟的程序交易系统的思路 z
波涛(1998)在<系统交易方法>中提出,一个设计良好的交易系统,必须对投资决策的各个相关环节做出相应明确的规定,同时还必须符合使用者的心理特征.投资对象的统计特征以及投资资金的风险特征. ...
- 还能输入多少字?(JS动态计算)
<div class="m-form ovh"> <div class="hd"> <span class="fr&qu ...
- Text Reverse
Text Reverse Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Tota ...
- devi into python 笔记(七)locals与globals 字典格式化字符串 字符集
locals()与globals(): """ locals:局部命名空间 globals:全局命名空间 都是以dictionary的形式保存的,变量名是键,变量值是值 ...
- HDOJ/HDU 5686 Problem B(斐波拉契+大数~)
Problem Description 度熊面前有一个全是由1构成的字符串,被称为全1序列.你可以合并任意相邻的两个1,从而形成一个新的序列.对于给定的一个全1序列,请计算根据以上方法,可以构成多少种 ...