外会响应触摸的区域(用bounds
最初的坐标)写成数组进行赋值。例如,frame
为(30, 0, 100, 100)
,要让左边宽 30、高 100 的区域为响应区域,则给interactionAreaNotInBounds
赋值为[CGRect(x: -30, y: 0, width: 30, height: 100)]
。
当要分页的页数较少、每页内容不多的时候,可以用这个方法实现。如果要显示很多页的内容,一次把所有分页视图加到 scroll view 上,影响性能。这种情况可以用UICollectionView
实现,UICollectionViewCell
是重用的,节约资源。用UICollectionView
实现的方法不同。
UICollectionView 分页
如果UICollectionView
用以上的方法实现,出现的问题是,不在bounds
之内的UICollectionViewCell
可能消失。因为 cell 是重用的,移出bounds
之后可能就被移除而准备重用。UICollectionView
继承自UIScrollView
,可以通过UIScrollViewDelegate
的方法,模拟分页效果。具体实现方法与分页大小有关。
分页较大
当分页较大时,比如水平滚动,一页宽度大于屏幕宽度一半,每次滚动的最远距离就限制到相邻分页。这样的限制与isPagingEnabled
的效果基本符合。实现UIScrollViewDelegate
的一个方法即可。
private var selectedIndex: Int = 0 // index of page displayed
private let cellWidth: CGFloat = UIScreen.main.bounds.width - 100
private let cellHeight: CGFloat = 100
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// Destination x
let x = targetContentOffset.pointee.x
// Page width equals to cell width
let pageWidth = cellWidth
// Check which way to move
let movedX = x - pageWidth * CGFloat(selectedIndex)
if movedX < -pageWidth * 0.5 {
// Move left
selectedIndex -= 1
} else if movedX > pageWidth * 0.5 {
// Move right
selectedIndex += 1
}
if abs(velocity.x) >= 2 {
targetContentOffset.pointee.x = pageWidth * CGFloat(selectedIndex)
} else {
// If velocity is too slow, stop and move with default velocity
targetContentOffset.pointee.x = scrollView.contentOffset.x
scrollView.setContentOffset(CGPoint(x: pageWidth * CGFloat(selectedIndex), y: scrollView.contentOffset.y), animated: true)
}
}
selectedIndex
表示当前分页序号,默认显示最左边的一页,因此初始化为 0。如果最开始显示其他页,需要改变selectedIndex
的值。通过selectedIndex
的值,将要停下来的坐标x
,计算出位移movedX
。当位移绝对值大于分页宽度的一半时,滚动到位移方向的相邻页。
给targetContentOffset.pointee.x
赋值,改变滚动终点的x
坐标。宽度较大的分页效果滚动速率不能太慢,所以当速率小于 2 时,给targetContentOffset.pointee.x
赋值为当前位置即停止滚动,调用setContentOffset(_ contentOffset: CGPoint, animated: Bool)
方法,立即以默认速度滚动到终点。
现在,还有一个小问题,就是滚动到最后一页时,滚动停止的位置不固定。最后一页停止的位置有时候靠屏幕左边,有时靠右。从最后一页往回滚动可能会有点奇怪(突然加速)。解决办法是增加一个UICollectionViewCell
放到最后,cell 的宽度为屏幕宽度减分页宽度,使最后一页滚动的停止位置都靠屏幕左边。假设分页数量(UICollectionViewCell
的数量)为numberOfItems
,以下是 cell 的大小
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
switch indexPath.item {
case numberOfItems:
return CGSize(width: UIScreen.main.bounds.width - cellWidth, height: cellHeight)
default:
return CGSize(width: cellWidth, height: cellHeight)
}
}
分页较小
当分页较小时,屏幕宽度可以显示好几个分页,就不能把滚动距离限制到相邻分页。直接判断滚动终点离哪个分页比较近,以近的分页为终点。
private let cellWidth: CGFloat = 100
private let cellHeight: CGFloat = 100
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// Destination x
let x = targetContentOffset.pointee.x
// Page width equals to cell width
let pageWidth = cellWidth
// Destination page index
var index = Int(x / pageWidth)
// Check whether to move to next page
let divideX = CGFloat(index) * pageWidth + pageWidth * 0.5
if x > divideX {
// Should move to next page
index += 1
}
// Move to destination
targetContentOffset.pointee.x = pageWidth * CGFl