Andy矢倉

老板 来杯董小姐 去冰 少糖 多放点小姐

曾梦想 仗贱走天涯 看尽女人和繁华


iOS手把手搭建·无限循环滚动视图

在大多数常规App开发当中,我们都会有产品运营栏的需求,也就是列表页或者产品顶部,又或者整个页面需要展示几个滚动的运营活动、产品、广告什么的,当然,也可能是一个自己实现的一个图片浏览器。
在早些年,这类需求大多都是从First逐个滚动到Last,然后再自动滚到First,技术上无非都是通过UIScrollView + Timer的方案,iOS开发往往都喜欢专注于用(xuan)户(ji)体(zhuang)验(bi)的,所以后来出现了无限循环滚动的体验。



得益于iOS6以后出现的UICollectionView控件,无论是滚动视图,还是做图片浏览,都降低了很多难度和代码量,但是它为了灵活性,官方没有做无限滚动Api,那么今天,我们就用UICollectionView来实现无限循环滚动视图。

这里使用UICollectionView管理Cell方式来减少代码量和复用Cell的内存优化,通过关闭scrollToItem(at:at:animated:)滚动动画来让用户无法发觉是代码在控制滚动,让用户产生错觉变成无限循环。

我们假设视图是在水平滚动,Cell是横屏全部宽度填充,然后设置paging属性为true以便滚动到边缘从而获得更好的体验。

揣测

原理:这么做依赖于有操作表的概念,这样我们就可以在收尾添加元素。好比如,你有一个包含三个项目的数组,想要他们无限循环的滚动,那就把首位元素拷贝插入到末尾,同时末尾元素拷贝一份插入到首部。演示如下:



OK,我们直接上代码:

1
2
3
4
5
6
7
8
9
private func setupDataForCollectionView() {
let originalItems = ["One", "Two", "Three"]
if let firstItem = originalItems.first, let lastItem = originalItems.last {
var workItems = originalItems
workItems.insert(lastItem, at: 0)
workItems.append(firstItem)
items = workItems
}
}

那么我们得到的items的内部结构就是这样:

1
["Three", "One", "Two", "Three", "One"]

结构上就和假想图一致。

臆测

这个过程依赖于在首尾的indexPath需要关闭动画来实现,通过方法scrollToItem(at:at:animated:)实现。
该方法包含以下三个参数:

  • indexPathCollectionView滚动到的位置。
  • UICollectionViewScrollPosition来控制CollectionView应该滚动到什么位置。
  • animated这个布尔值控制是否展示动画。

UICollectionViewScrollPosition控制滚动位置,假设CollectionView被设置了分页,如果是水平视图,我们希望它滚到左边那就为UICollectionViewScrollPosition.left,如果是垂直视图,希望它滚到顶部那就是UICollectionViewScrollPosition.top

下面我们来看看是演示情况:



如果是往前滚的话就正好相反:



实施

我们实现的关键技术点就是检测用户的滚动意图,这样才能触发scrollToItem(at:at:animated:)方法来实现我们的目的。
为了能做到这一点,我们需要实现UICollectionView的父类的UIScrollView的代理方法scrollViewDidEndDecelerating来检测滚动停止信号。
再通过检测contentOffset属性来判断具体位置。

1
2
3
4
5
6
7
8
9
10
11
override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let contentOffsetX = scrollView.contentOffset.x
let contentOffsetWhenFullyScrolledRight = (collectionView?.frame.width)! * CGFloat(items.count - 1)
if contentOffsetX == contentOffsetWhenFullyScrolledRight {
let indexPath = IndexPath.init(item: 1, section: 0)
collectionView?.scrollToItem(at: indexPath, at: .left, animated: false)
} else if contentOffsetX == 0 {
let indexPath = IndexPath.init(item: (items.count - 2), section: 0)
collectionView?.scrollToItem(at: indexPath, at: .left, animated: false)
}
}

OK,我们来看下视图结构和对应的索引结构:



视图结构能清晰的解答:

  • 如果在我们滚到最右边,所看到的元素为我们拷贝的第一个元素,那么就应该调用scrollToItem(at:at:animated:)方法来滚动到实际上的第一个元素位置,也就是索引[0, 1]的位置。
  • 如果我们滚动到最左边,所看到的元素为我们拷贝的最后一个元素,那么就应该调用scrollToItem(at:at:animated:)方法来滚动到实际上的最后一个元素位置,也就是索引[0, 3]的位置,也就是处理位置里的items.count - 2位置。

总结

创建一个无限循环的滚动视图其实So Easy,也就五个步骤:

  • 根据实际数据,填充收尾的假数据。
  • 检查滚动视图滚动停止时的偏移位置。
  • 如果滚动到最末尾,则移动到填充过的数据项中的第二项。
  • 如果滚动到最首位,则移动到填充过的数据项中的最后一个数据项。
  • 保证方法scrollToItem:里的animated动画参数为关闭。
最近的文章

iOS 模拟定位,随时随地钉钉打卡

本文只使用有Mac电脑,用iPhone的骚年们。本章是水文,利用Xcode模拟定位打卡很早就有教程了,这里干货只有一行,离开Xcode任然保证模拟定位不变。 对象:钉钉等LBS应用近两年很多企业和中小型公司都开始使用钉钉打卡签到。很多苦逼党因为坐公交晚了几分钟,被扣钱,晚了几分钟,全勤没了,所以 …

于  GPS, Location, Xcode, iOS 继续阅读
更早的文章

UITableViewCell - 自适应速成大法

原文地址:https://www.raywenderlich.com/129059/self-sizing-table-view-cells转载地址:http://www.rockerhx.com/2016/12/12/2016-12-12-Self-sizing-Table-View-Cells/ 最近滚回成都忙房子的事情,一直没时间更新,今天在群里开到好多人在聊很多培训机构都已经辞掉iOS培训老师,关闭iOS培训课程。这尼玛回想在乔帮主时代,开发iOS还是件可以装好几年逼,而如今,日落黄昏啊。纵观近几年,有太多培训机构当搅屎棍进来瞎掺和贵国的全民创业,伪造简历不说,那帮菜鸟的技术真的是辣眼睛,卧槽,好像跑偏了,今天不是来申讨培训党的,我们是正经教程,还是回到主路继续开车~~~ …

于  Cell, TableView, iOS 继续阅读