这两天使用Reveal工具查看"手机淘宝"App的UI层次时,发现其图片轮播使用了三个UIButton的复用来实现的图片循环无缝滚动。因而乎就有了今天这篇博客,看到“手机淘宝”这个幻灯片的UI层级时,就想要动手使用三个Button来实现一下,固然本篇博客使用是Swift语言,思路就是使用三个Button进行图片无限轮播。以前发过两篇关于图片轮播的博客,一个是没有使用ImageView复用的,一个是使用两张ImageView进行复用来实现图片轮播的,都是使用的Objective-C实现的,并在github上进行了代码分享。html
本篇博客就是介绍如何去一步步的封装这个三个Button的无限轮播的组件的。在实现时将该无限轮播的组件进行了封装,须要你在实例化该组件后,传入一个存放图片地址的数组,数组中能够是本地图片的名字,也能够是一个网络图片的地址。若是是网络图片的地址,组件中会使用NSRULSession并采用合适的缓存策略从网络加载图片,加载完后在咱们的组件上进行显示。废话少说,进入咱们今天的主题。git
今天的博客彻底是我的兴趣爱好,也不是公司中项目要使用的缘故才去实现的。就是看到了淘宝中的轮播图,从UI层级上感受这样能够实现,因而乎就打开Xcode,建立个Swift工程实现一把,过程仍是蛮愉快的。固然今天封装组件的名称是以公司的名称首字母作的前缀了,这也便于在之后的工做中遇到该问题直接拿过来就用呢。github
1、对“手机淘宝”UI层次的简单分析数组
下方截图就是当时我用Reveal工具查看的手机淘宝App的UI层级,下方只是幻灯片的部分。下方整个页面是使用UICollectionView来实现的了,下方的幻灯片所在的UIScrollView就放在UICollectionView上的一个Cell上。固然这不是咱们今天的重点,咱们将目光转移到左边红框中的数学层级上。能够明显的看出UIScrollView上贴了三个UIButton,而每一个UIButton上又贴了一个UIImageView。缓存
须要注意的一点是你看到UIButton是三个Button的中间一个,从下面UI视图中咱们不难分析出,不管当前显示的是第几张图片,你看到的永远是中间Button上显示的图片。也就是将要显示的图片放到中间的Button上,左边的Button存放上一张图片,右边的Button存放下一张图片。当用户左划或者右划后,咱们要作的事情就是将中间按钮放到可视区,而且设置成要显示的图片,固然左右两个Button都得设置成相应的图片,便于用户下次移动。固然这只是个人我的猜想,上面这种思路使用代码来实现是彻底可行的,而且能够实现无循环无缝滚动了。网络
固然,上面说的原理比较简单,具体使用代码实现起来仍是须要许多细节的。好比用户滑动时定时器的挂起和唤醒,用户左右滑动方向的记录,便于自动轮播时的方向与用户上次滑动的方向一致,异步加载网络请求图片,加载后显示在相应的Button上,经过Closure回调出用户点击事件可当前图片的索引信息等等须要注意的细节。敲代码也有几年时间了,有一点感触挺深的就是“说和作”彻底是两码事,也就是你有思路和你把你的思路用代码实现,这是彻底不一样的概念。固然正确的思路当然重要,可是不去实现再好的思路也是枉费,实践出真知吗。说这么多,有回到了说与作的问题上,这话题就很少讨论了,高执行力,高自制力好处是多多的。言归正传,回到今天的主题。闭包
2、从“原理图”中来看今天轮播的主题异步
下方就是今天要实现的轮播组件的原理图,思路就是这么个思路,要实现起来还得靠代码往上摞呢。下方解析图是以轮播5张图片为例。初始化状态是你看到的是第一张图片,第一张图片位于三个Button的中间,而后左边就是最后一张图片,右边是第二张图片。若是用户想右滑动显示的第二张图片(对应着下方的第一步),当第二张图片已经彻底显示出来后,咱们要作的第一件事情就是将用户移动的位置进行复位,也就是将第二个Button移动到显示的位置,而后设置按钮上显示相应的图片。第一个按钮就显示当前图片的前一张图片,若是当前显示的是第一张图片,那么第一个按钮上就显示最后一张图片。第三个按钮就显示当前图片的下一张图片,若是当前显示的图片是最后一张图片的话,那么第三个按钮上就显示第一张图片。这样就能够图片轮播了。函数
3、该自定义控件的视图层级工具
下图就是咱们按照上面的思路,使用ScrollView上添加3个Button来实现的图片轮播,无缝滚动的效果仍是蛮OK的。在下方运行效果中美女图片是从本地加载的,而风景图片是使用NSURLSession和GCD的东西并行异步的从网络获取的,获取完后再加载到相应Button的ImageView上。下方就是咱们本篇博客相关Demo的运行效果,单从效果上来看,与以前所发布的图片轮播没有什么区别,可是其内部实现机制仍是有较大区别的。接下来就逐步的看一下代码实现。
上面是运行效果,下方是UI的层级。固然也是用咱们强大的Reveal来查看的了,下方红框中就是咱们图片轮播的视图层级。该视图层级与“手机淘宝”上是一致的,都是在UIScrollView上贴了三个Button,而后在每一个Button上贴了一个ImageView。层级比较简单,而你看到的固然是中间Button上的ImageView了,更多细节请看下图。
4、代码实现
运行效果看完了,UI层级结构也看完了,那么接下来就到了咱们代码实现的时刻了。下方从组件的调用方式入手,逐步的去看一下上述效果是如何实现的。
1.上述组件的调用方式
下方代码段就是上述组件的实例化和调用方式,首先进行初始化,而后将该组件添加到父视图上。以后将要显示的图片数组imagesNameArray传入组件中,最后设置一下组件的闭包回调便可,该回调将每一个按钮点击的时间回调给组件的使用者,该Closure的参数是当前点击按钮上所显示的Image的索引,说白了也就是你当前点击的第几张图片。
在调用组件时,传入给组件的参数是一个数组,下方代码就是咱们初始化imagesNameArray的函数。从该代码段中咱们不难看出,该数组中存储的有本地图片的名字,也有UIImage的对象,也有网络图片的URL。将含有三种元素的数组传给咱们的组件实例,这些数组中的资源就能够按照数组中的顺序依次的循环轮播了。
2.上述组件的核心代码
组件调用方式仍是蛮简单的,看完调用方式,接下来来看一下具体的代码实现。下方是咱们组件中比较核心的实现部分。
(1).异步加载网络图片
下方代码段就是遍历咱们的ImagesNameArray,找出该数组中全部的网络图片的URL,每找到一个URL就开启一个线程来请求该地址中的图片。下方的queue是并行队列,此处固然是并行队列的异步执行了。关于GCD的东西,请参考以前的博客《Grand Central Dispatch详解》。
下方代码段就是requestImage()方法中的内容了,该方法中使用NSSession并配置相应的缓存策略来请求网络图片。加载完图片后,将ImagesNameArray中相应的URL替换成相应的UIImage对象,而后在主线程中更新UI显示相应的图片,具体代码以下所示。
(2)moveImageView()方法的实现
该方法负责将按钮归位与赋值,该方法对应着上述原理图的第二和第三步。上面网络请求完一张图片后就会调用该方法更新UI。该方法的具体实现以下所示。下方代码中首先获取当前显示的页数,也就是当前显示的Image的索引,而后将第二个Button移动到可视区,最后调用setButtonImage()函数将每一个Button上的ImageView设置成相应的Image。
(3)、定时器的实现
图片要隔段时间自动轮播,此处咱们使用的是dispatch_source中的定时器类型来实现的自动轮播。下方就是咱们定时器的实现,以下所示,代码比较简单,在此就不作过多的赘述了,建立完定时器咱们不要忘了对定时器的source进行唤醒便可。
当用户进行手动切换时,咱们要对定时器进行相应的挂起和唤醒操做。也就是说当用户开始滑动时咱们要对定时器进行挂起,当用户滑动结束后要对定时器进行唤醒。固然这些都是在ScrollView相应的代理方法中进行处理的,具体以下所示:
(4)、滑动结束后更新按钮的位置和图片
不管是手动滑动,仍是使用定时器滑动,滑动结束后咱们都须要更新一下按钮的位置和按钮上要显示的图片。因此咱们还须要使用到UIScrollView上的一个代理方法,那就是scrollViewDidScroll(),在该代理方法中咱们调用了moveImage()方法来更新Button的位置和Button上ImageView要显示的图片信息。没有这个方法,图片就动不起来了,具体代码以下所示:
上述是该Demo的核心代码,更为详细的代码请移步于github: https://github.com/lizelu/CEImagesScrollDisplay, 因本篇博客篇幅有限,更具体的细节在此就不作过多赘述了,Demo已在上述github上进行分享。