ios中UIScrollView的基础知识

本文内容参考了Apple官方文档

无论学什么知识,我觉得最开始最重要的还是学这个东西的概念,然后再是他的用法,最后才是自己的拓展和对其思考。对于iOS知识也一样,所以我们可以通过Apple的官方文档来看看一些概念和用法。很多年以前自己初入iOS编程的时候,也是项目用到什么就学什么,可是现在我总觉得有必要重读一些Apple的文档,把一些基础的概念弄清楚,所以也想把这些记录在此,方便自己以后的温故而知新,也方便其他有需要的同学。以下内容翻译自Apple官方文档,并根据自己的理解做了些必要的补充,方便自己和他人的理解。

UIScrollView介绍

总体而言,UIScrollView就是一个可以滚动其包含的子视图,也可以放大缩小其包含的子视图的视图。

UIScrollView是好几个UIKit类的父类,譬如UITableView,UITextView。

UIScrollView的主要特性就是他内容的起点会根据他的内容视图随时调整。一般而言,通常都是以应用主窗口的大小来作为UIScrollView的大小(但这不是必须的,我们可随意调整大小)来裁剪他内部的内容视图,以此来显示内容。UIScrollView也会跟踪手指的移动并以此来调整内容视图的起点。内容视图会根据因为手指移动而偏移的新的起点来绘制UIScrollView可见部分的内容。UIScrollView本身并不绘制什么东西,除了展示垂直或水平滚动的指示器。UIScrollView必须知道内容视图的size,因为这样他才能知道什么时候该停止滚动。默认情况下,当滚动超过了内容视图的边界,它会自动反弹回来。

在UIScrollView里用来管理视图绘制和展示的对象,会将所有子视图铺在一起来保证没有视图会超出屏幕的尺寸。当用户用手指滚动的时候,这个对象会根据需要添加和移除对应的子视图,来保证UIScrollView显示图像的正确性。

因为UIScrollView不像电脑的图表一样,没有可操作的滚动条,所以他必须要知道一个触摸是否表示要滚动,还是表示跟踪对应的子视图。为了达到这个目的,它启动了一个计时器,并临时截获了touch-down事件,在计时器开始激活之前,会看一下被截获的触摸手指是否会有其他的移动。如果计时器被激活的时候,这个触摸手指没有有效的位置改变,UIScrollView会给被触摸的子视图发送跟踪事件。如果在计时器结束之前,用户手指拖动了足够远的距离,那么UIScrollView会取消任何对子视图的跟踪并且执行滚动操作。子类化UIScrollView,可以重载touchesShouldBegin(_:with:in:),isPagingEnabled, 和touchesShouldCancel(in:)这几个方法来影响scroll view如何处理滚动手势。

UIScrollView也可以放大缩小以及平移它里面的内容视图。当用户使用一个pinch-in或者pinch-out手势的时候,scroll view会调整内容视图的偏移和放大缩小的比例。当手势结束的时候,管理内容视图的对象就会根据需要更新包含的子视图。当手势还在进行中的时候,scroll view不会对子视图发送任何跟踪消息。

UIScrollView类可以拥有一个代理,这个代理必须遵循UIScrollViewDelegate协议。为了使缩放以及平移操作都能顺利进行,代理必须实现viewForZooming(in:)scrollViewDidEndZooming(_:with:atScale:)这两个方法。 除此之外, 缩放的最大比例和最小比例必须不一样。

UIScrollView的属性和相关API

响应scroll view交互

1
var delegate: UIScrollViewDelegate?

scroll view的代理对象

1
protocol UIScrollViewDelegate

遵循了这个协议的对象就是UIScrollView的代理对象,可以通过实现这个协议里方法来自定义很多UIScrollView的操作,譬如滚动,放大缩小,滚动动画等等

管理内容尺寸和偏移

1
var contentSize: CGSize

整个scroll view内容视图的尺寸,默认是(0,0)。其实就是scroll view可以滚动范围的大小,如果这个范围不大于scroll view的frame,那么scroll view 就不能滚动,contentSize必须大于scroll view的frame才可以滚动。

1
var contentOffset: CGPoint

当前内容视图的起点相对于整个scroll view的起点的偏移量,x轴方向的偏移以及y轴方向的偏移。

1
func setContentOffset(CGPoint, animated: Bool)

通过这个方法可以主动设置contentOffset,并且可以用动画来展示这个转变。譬如你希望在展示scroll view的时候内容视图不在默认的起点,而是希望已经往下滚动了100,那么我们可以用这个函数来设置一个y轴方向的偏移量,并可以用动画展示它。

管理内容嵌入的行为

1
var adjustedContentInset: UIEdgeInsets{get}

这是一个只读属性,用来获取被调整后绘制内容视图的区域,可以理解为内容视图距离scroll view四周边界的距离,可以使用contentInsetAdjustmentBehavior这个属性来决定被调整的区域是否包含安全区域的inset。如果包含了,那么安全区域的inset会被自动加入到contentInset这个属性中,并且由adjustedContentInset获得最终的值

1
var contentInset: UIEdgeInsets

用户自定义的内容视图和安全区域或者scroll view边界之间的距离。默认的上下左右距离都是0,但是我们可以用它来扩展我们的内容视图和scroll view或者安全区域之间上下左右的间隔。

1
var contentInsetAdjustmentBehavior: UIScrollView.ContentInsetAdjustmentBehavior

这个属性会指定安全区域的inset会如何被用来影响内容视图的inset,默认值是ContentInsetAdjustmentBehavior.automatic,自动调整scroll view内容视图的insets。它还有其他几个枚举,如下:

scrollableAxes:只在可滚动的方向调整insets

never:从不调整scroll view的insets

always:在内容视图的调整中总是包含安全区域的insets

1
func adjustedContentinsetDidChange()

调整后的内容视图的insets如果发生改变,那么这个方法就会被调起。

获取布局指南

1
var frameLayoutGuide: UILayoutGuide{ get }

这是个只读属性,当我们想要明确的使用相对于scroll view本身,而不是相对于他的内容视图的布局约束的时候,我们就要使用这个属性来进行auto layout的约束设置。

1
var contentLayoutGuide: UILayoutGuide{ get }

和上面的属性一样,这也是一个只读属性,不过区别在于,当你想要相对于scroll view的内容视图做auto layout约束布局的时候,就要用这个属性来设置。

对scrollVIew的配置构造

1
var isScrollEnabled: Bool

这是一个布尔值属性,用来决定scrollView是否可以滚动,默认是true。

1
var isDirectionalLockEnabled: Bool

这是一个布尔值属性,默认值为false,用来决定用户拖拽的时候,scrollView是否在指定的方向不能滚动,也就是只能在一个方向拖拽。当值为false的时候,在水平和垂直方向都可以滚动;当值为true,并且用户在某一方向(水平或垂直)拖拽的时候,那么scroll view在另一方向就不能滚动了。如果用户拖拽的方向是斜的,那么滚动方向就不会被锁定,用户可以在任何方向拖拽直到拖拽完成。

1
var isPagingEnabled: Bool

这是一个布尔值属性,用来决定scrollView的滚动是否是按页滚动,默认是false。

1
var scrollsToTop: Bool

布尔值属性,用来决定滚动到顶部的手势是否可用。Apple自带的用手指点击status bar的手势就是将scroll view自动滚动到顶部。默认值true,自动打开这个手势的。如果设为false,那么scroll view 的代理方法scrollViewShouldScrollToTop(_:)会返回false,或者内容本身已经在顶部了,那就什么都不回发生。

在滚动到顶部后,代理方法scrollViewDidScrollToTop(_:)就会被调用。

在Iphone上,如果屏幕上有多于一个的scroll view,那么这个手势就不起作用了。

1
var bounces: Bool

布尔值属性,用来控制当scroll view滚动超过了内容视图的边界的时候,是否会自动弹回。默认值是true,用户可以滚动使scroll view 超出内容视图的边界,但是放手后会自动弹回。如果false,那么当滚动到边界的时候,就会立马停止滚动,无法超出边界,更不会弹回。

1
var alwaysBounceVertical: Bool

布尔值属性,用来决定当垂直方向滚动到内容结尾的时候,是否一直可以发生弹回。默认值是false,如果设为true,并且bounces属性也为true,那么垂直方向的拖拽总能发生弹回,即便内容视图的尺寸小于scroll view的边界。

1
var alwaysBounceHorizontal: Bool

布尔值属性,用来决定当水平方向滚动到内容结尾的时候,是否一直可以发生弹回。默认值是false,如果设为true,并且bounces属性也为true,那么水平方向的拖拽总能发生弹回,即便内容视图的尺寸小于scroll view的边界。

获取滚动状态

1
var isTracking: Bool { get }

布尔值只读属性,用来告知用户是否开始触摸内容并准备开始滚动。返回值为true,表明用户已经触摸了内容,但可能还没有开始拖拽,但已经准备开始滚动了。

1
var isDragging: Bool { get }

布尔值只读属性,用来表明用户是否已经开始拖拽滚动内容。

1
var isDecelerating: Bool

布尔值只读属性,用来反馈当用户举起手指的时候,内容视图是否依然在滚动。当用户用手指在scroll view上拖拽了一下,然后手指离开屏幕,然后scroll view依然在减速滚动的时候,那么这个属性就会返回true。

1
var decelerationRate: UIScrollView.DecelerationRate {get set}

float类型的值,用来决定当用户滑动离开后,scroll view 减速的速率。在iOS12中,可以用两个常量normalfast来决定它的速率。

管理滚动指示器和刷新控制

1
var indicatorStyle: UIScrollView.IndicatorStyle

滚动指示器的样式,默认是是UIScrollView.IndicatorStyle.default,还有blackwhite的选项。

1
var scrollIndicatorInsets: UIEdgeInsets{ get set }

滚动指示器距离scroll view边界的距离,默认都是UIEdgeInsetsZero,就是都是0。这个属性主要用来自定义滚动指示器的位置。

1
var showsHorizontalScrollIndicator: Bool { get set }

布尔值属性,用来控制水平方向的滚动指示器是否显示,默认值是true。

1
var showsVerticalScrollIndicator: Bool { get set }

布尔值属性,用来控制垂直方向的滚动指示器是否显示,默认值是true。

1
func flashScrollIndicators()

当你想要立马显示你的滚动指示器的时候,就调用这个方法,滚动指示器会显示了一下后,然后慢慢消失。

1
var refreshControl: UIRefreshControl?{ get set }

从iOS10以后,UIScrollView也拥有了refreshControl。

1
func scrollRectToVisible(CGRect, animated: Bool)

滚动到内容视图中的一个指定区域,让它刚刚好可以在屏幕的可见范围内。如果这个指定的区域已经在可见范围内了,那这个方法不会做任何事情。

管理触摸

1
func touchesShouldBegin(_ touches: Set<UITouch>, with event: UIEvent?, in view: UIView) -> Bool

当一个手指触摸按下内容视图的时候,如果你想自定义一些默认行为,那么你就需要子类化UIScrollView,并重载这个方法。如果你不希望scroll view把事件消息传递给对应的view,那就返回false,不然就返回true。UIScrollView的默认行为是调用对应subview的UIResponder的事件处理方法来处理。

1
func touchesShouldCancel(in view: UIView) -> Bool

当scroll view开始对内容视图发送跟踪消息之后,这个方法会被调用。如果返回false,scroll view会停止拖拽并且将触摸事件往前传递给对应的subview。如果返回true,那么就是取消更深层次的消息传递。如果触摸的view不是UIControl对象,那么默认返回值是true,否则默认返回值是false。如果CanCancelContentTouches这个属性是false,那么scroll view 就不会调用这个方法。

1
var canCancelContentTouches: Bool { get set }

布尔值属性,用来控制在内容视图上的触摸是否总是一个消息追踪。

如果这个值是true并且一个子视图已经开始追踪一个触摸它的手指了,并且这个时候用户拖拽这个手指足够长的距离,这个距离完全可以形成一次滚动,这个view会收到一个touchesCancelled(_:with:)消息,然后scroll view会将这个触摸处理为滚动而不是追踪。如果这个值是false,那么内容视图里的子视图只要开始了追踪,不管手指如何移动,scroll view都不会滚动。

1
var delaysContentTouches: Bool { get set }

布尔值属性,用来决定scroll view 是否延迟处理touch-down手势。

如果这个属性是true,那么scroll view会延迟处理touch-down手势,直到可以确定这个手势的意图就是滚动。如果false,那么scroll view认为这是追踪意图,scroll view 会立即调用touchesShouldBegin(_:with:in:)。默认值是true。

缩放和平移

1
var panGestureRecognizer: UIPanGestureRecognizer { get }

只读属性,平移手势识别器。如果我们需要更精确的平移手势操作,我们就需要用到这个属性。

1
var pinchGestureRecognizer: UIPinchGestureRecognizer? { get }

只读属性,捏合手势识别器。如果我们需要更精确的捏合手势操作,我们就需要用到这个属性。

1
func zoom(to rect: CGRect, animated: Bool)

缩放内容视图到一个指定大小的区域,好让它在设备上可见。这个方法会将内容视图缩放到指定的rect,并且会根据需要自动调整zoomScale。在实现相应的代理方法后,可通过这个函数来进行缩放。

1
var zoomScale: CGFloat {get set}

float类型属性,用来指定当前scroll view的缩放系数。默认值是1.0

1
func setZoomScale(_ scale: CGFloat, animated: Bool)

设置新的缩放系数,并可以选择用动画来执行这个改变。也是在实现相应的代理方法后,可通过这个函数来进行缩放。

1
var maximumZoomScale: CGFloat {get set}

用于缩放的最大系数,决定了内容视图可以被放大到多大,如果可以缩放,那么这个值必须比minimum zoom scale大。默认值是1.0

1
var minimumZoomScale: CGFloat {get set}

用于缩放的最小系数,决定了内容视图可以被缩小到多小,如果可以缩放。默认值是1.0

1
var isZoomBouncing: Bool{get}

布尔值只读属性,用来表示缩放已经超出了设定的最大或最小的限制。如果scroll view已经缩放到最大或最小,那么会被弹回到设定的最大和最小系数,这个时候返回true,否则返回false。

1
var bouncesZoom: Bool{get set}

布尔值属性,用来决定当scroll view缩放超出最大或最小限制后,是否展示弹回的动画。如果是true,那么当缩放超出最大或最小限制后,scroll view 会临时用动画展示超出限制的缩放,如果松开手指,那么会展示退回到限制的最大或最小缩放系数的弹回动画。如果这个值是false,那么当scroll view被缩放到最大或最小限定系数后,会立即停止缩放,也不会有动画。默认值是true。

管理键盘

1
var keyboardDismissMode: UIScrollView.KeyboardDismissMode{get set}

当一个拖拽在scroll view 上发生时,键盘消失的方式。这是一个枚举类型。

none:当一个拖拽发生时,键盘不消失

onDrag:当一个拖拽发生时,键盘消失

interactive:交互型,键盘在拖拽触离开屏幕之后,也会消失,但可以再次向上拉,以取消关闭。

管理索引

1
var indexDisplayMode: UIScrollView.IndexDisplayMode{get set}

当用户在滚动的时候,索引显示的方式。这是一个枚举值。

automatic:索引的显示与否会根据适当的情况,自动决定。

alwayshidden:索引不显示

UIScrollViewDelegate相关基础知识

UIScrollViewDelegate这个协议所定义的方法允许遵守此协议的对象响应UIScrollView的一些消息,以此来响应并影响一些操作,譬如滚动,缩放,减速滚动内容还有滚动动画等。

响应滚动和拖拽

1
optional func scrollViewDidScroll(_ scrollView: UIScrollView)

当用户在滚动内容视图的时候,如果实现了这个方法,这个方法会被调用来告知代理对象,用户在滚动视图了。比较代表性的用法是,代理对象实现这个方法用来获得content offset的变化,然后绘制内容视图受影响的部分。

1
optional func scrollViewWillBeginDragging(_ scrollView: UIScrollView)

当scroll view将要开始拖拽滚动的时候,会告诉代理对象。代理对象可能直到拖拽发生了一小段距离才会收到这个消息。

1
optional func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)

当用户将要完成拖拽滚动的时候告诉代理对象。

1
option func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool)

当拖拽结束的时候告诉代理对象。这个方法在上面的方法之后被调用,当用户的手指在拖拽后离开屏幕的时候,scroll view会发送这个消息给代理对象。willDecelerate如果是true,说明还在滚动,但是在减速。如果是false,scroll view在手指离开后会立即停止滚动。之前介绍的isDecelerating这个属性能够控制scroll view是否减速。

1
optional func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool

询问代理对象,scroll view是否可以滚到内容顶部。返回true,表明允许,false则不允许。

如果代理对象没有实现这个方法,那么系统默认是允许的。如果要让点击satus bar返回顶部的手势有效果,scrollsToTop这个属性必须被设置为yes。如果这个方法返回false,那么即便属性scrollsToTop是true,也不会有效果。

1
optional func scrollViewDidScrollToTop(_ scrollView: UIScrollView)

当scroll view已经滚动到内容顶部的时候,会立即向代理发送这个消息。

1
optional func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView)

当scroll view将要开始减速滚动动作的时候,会告诉代理对象。scroll view在用户手指抬起,但scroll view依然在滚动的时候,会调用这个代理方法。

1
optional func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)

当scroll view将滚动动作减速到停止后,会告诉代理对象。

管理缩放

1
optional func viewForZooming(in scrollView: UIScrollView) -> UIView?

当scroll view发生缩放的时候,scroll view会通过这个方法询问代理对象需要缩放的是哪一个view。如果你不想缩放有效果,可以返回nil。

1
optional func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view:UIView?)

当在scroll view里内容视图缩放将要开始的时候,会通过这个方法告诉代理对象。这个方法在缩放手势开始的时候,或者通过缩放比例改变而实现动画的时候都会被调用。我们可以用这个方法,在缩放内容之前,来存储一些状态信息或者执行一些额外操作。

1
optional func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat)

当scroll view里的内容视图完成缩放的时候,会通过这个方法告诉代理对象。scroll view在任何弹回动画之后,或者改变缩放比例来实现动画之后,或者任何缩放相关的手势结束之后,都会调用这个方法。

1
optional func scrollViewDidZoom(_ scrollView: UIScrollView)

当scroll view的缩放系数改变的时候,也就是正在缩放的时候,会通过这个方法告诉代理对象。

响应滚动动画

1
optional func scrollViewDidEndScrollingAnimation(_ scrollView)

当一个滚动动画结束的时候,会通过这个方法告诉代理对象。当用户执行了setContentOffset(_:animated:)或者scrollRectToVisible(_:animated:)方法后,并且他们的动画都是开着的时候,那么等他们的滚动动画结束的时候,就会调用本方法来通知代理对象。

响应Inset的改变

1
optional func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView)

当scroll view的内嵌数值(inset value)发生改变的时候,会通过这个方法通知代理对象。

Author: Uncle Peter
Link: http://yoursite.com/2017/01/24/ios中UIScrollView的用法/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.