设为首页 加入收藏

TOP

UIScrollView 和 UICollectionView 分页效果(一)
2017-10-12 12:02:04 】 浏览:10130
Tags:UIScrollView UICollectionView 效果

UIScrollView 和 UICollectionView 分页效果

UIScrollView可以滚动显示宽度或高度大于其bounds的内容。有些时候,需要有分页效果。每一页有统一的大小,相邻无缝水平或垂直排列。当水平或垂直滚动松开手后,会在其中一页完全显示的位置停下,滚动的距离是一页宽度或高度的整数倍。具体实现方法分两种情况讨论:分页大小等于、小于bounds大小。分页大小大于bounds大小的情况,不知道有什么应用场景,不讨论。

分页大小等于 bounds 大小

如果分页大小与 bounds 大小相等,把UIScrollViewisPagingEnabled属性设置为true即可。此属性的官方解释

If the value of this property is true, the scroll view stops on multiples of the scroll view’s bounds when the user scrolls.

每一页的大小为bounds的大小,每次水平或垂直滚动的距离是bounds宽度或高度的整数倍。

分页大小小于 bounds 大小

UIScrollViewUICollectionView实现的方法不一样,需要分别讨论。

代码已上传 GitHub:https://github.com/Silence-GitHub/PageScrollViewDemo

UIScrollView 分页

UIScrollViewclipsToBounds属性默认为true,超出bounds的子视图(超出部分)是看不到的。可以把clipsToBounds设置为false,把isPagingEnabled设置为true,把bounds设置为需要的分页大小,在视觉上就基本达到分页效果了。然而,这样会出现的问题是:

  1. 滚动条只在bounds以内显示(所以分页效果只是视觉上“基本达到”)
  2. UIScrollView显示的内容会超出所在UIViewControllerview所在范围,当UINavigationController发生 push 或 pop 时,可能会看到超出部分,不美观
  3. 触摸bounds以外的区域没有响应

对于第 1 个问题,需要隐藏滚动条,把showsVerticalScrollIndicatorshowsHorizontalScrollIndicator都设置为false。既然要分页效果,滚动条就没必要显示。可以用UIPageControl或自定义控件来显示当前分页在所有分页中的位置。非要显示滚动条的情况不讨论。

对于第 2 个问题,可以把当前所在UIViewControllerviewclipsToBounds设置为true;或者把 scroll view 放在另一个UIView上,把这个UIViewclipsToBounds设置为true

对于第 3 个问题,需要重载func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?方法。此方法的官方介绍

Returns the farthest descendant of the receiver in the view hierarchy (including itself) that contains a specified point.

此方法返回包含触摸点的最上层视图(UIView),没有则返回nil。触摸屏幕时,屏幕上的视图通过此方法寻找发生触摸的视图。

Points that lie outside the receiver’s bounds are never reported as hits, even if they actually lie within one of the receiver’s subviews. This can occur if the current view’s clipsToBounds property is set to false and the affected subview extends beyond the view’s bounds.

当触摸点在bounds之外,此方法返回nil,表示当前视图不是发生触摸的视图。这就是问题的原因。需要自定义UIScrollView,重载此方法,让此方法在bounds之外触摸当前视图也返回被触摸的视图。自定义类PageScrollView

class PageScrollView: UIScrollView {
    
    var interactionAreaNotInBounds: [CGRect] = [] // Use bounds coordinate system
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        clipsToBounds = false
        isPagingEnabled = true
        showsVerticalScrollIndicator = false
        showsHorizontalScrollIndicator = false
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // Bounds is changed when scrolling
        // Update interaction area not in bounds according to current bounds
        let bounds = self.bounds
        let areas = interactionAreaNotInBounds.map { (rect) -> CGRect in
            return CGRect(x: bounds.minX + rect.minX,
                          y: bounds.minY + rect.minY,
                          width: rect.width,
                          height: rect.height)
        }
        // Find area contains point
        for area in areas where area.contains(point) {
            // Check subview
            for subview in subviews {
                // Convert point from current coordinate system to that of subview
                let convertedPoint = convert(point, to: subview)
                // Hit-test subview and return it if it is hit
                if let view = subview.hitTest(convertedPoint, with: event) {
                    return view
                }
            }
            // Return self if no subview is hit
            return self
        }
        // No area contains point
        // Do super hit-test
        return super.hitTest(point, with: event)
    }
}

初始化PageScrollView并确定framebounds后,需要给interactionAreaNotInBounds属性赋值。把bounds

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇OtherViews系统控件 下一篇Swift应用案例 1.无限轮播

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目