需求是做一个轮播图的。好的吧,那就研究UIScrollView吧,其实是要研究子类UICollectionView的,但是先研究UIScrollView会比较好一点,而且也满足需求了。一般来说会结合UIPageControl来使用。
最后我的轮播图采用了第三方框架:https://github.com/MQZHot/ZCycleView#other-prototype
UIScrollView
遵守了好多协议啊
UIScrollView对象的核心就是它仅仅是一个视图,而这个“视图的原点”在它的“内容视图”上是可以调整的。所以核心就是 原点调整 ,基于“内容视图”的调整。内容视图就是展示内容的视图,app展示内容是:先是设备的window,接着是view的frame,然后再是view的content view。所以UIScrollView的核就是把content view 直接复制粘贴在 frame上的。
UIScrollView会跟踪客户手指手势的移动,同时相应地调整“你的视图”在”内容视图“上的原点
--The view that is showing its content “through” the scroll view draws that portion of itself based on the new origin, which is pinned to an offset in the content view.
通常来讲“你的视图”尺寸只是“内容视图”尺寸的N分之一,所以在UIScrollView的环境下,你是通过在长长的 content view 上的 N分之k处(即offset) 放置你的第k幅 “你的视图” 的原点,就类似于相机胶卷,放映动画一样。所以UIScrollView就跟踪客户的手势,然后计算手指划过之后要不要展示下一幅“你的视图”。
--The scroll view itself does no drawing except for displaying vertical and horizontal scroll indicators.
除了水平和垂直方向的指示符,UIScrollView没有其他方向的指示符展示了。
-- The scroll view must know the size of the content view so it knows when to stop scrolling;by default, it “bounces” back when scrolling exceeds the bounds of the content.
所以UIScrollView必须知道你想展示的视图的所有尺寸。默认地,当客户的手势超出总的内容视图范围时,UIScrollView的”bounces“属性会“回弹”视图一下,这样视觉上好看一点。
--To make this determination, it temporarily intercepts a touch-down event by starting a timer and, before the timer fires, seeing if the touching finger makes any movement.
对于在 内容视图 里平铺多个子视图的情况下,UIScrollView设置了一个定时器,每次客户触摸屏幕就会计时,如果在短暂时间内用户的手势移动不大,则判断客户在操作子视图,就会把跟踪事件发送给子视图。如果动作很大,则认为客户在滚动屏幕。
--Subclasses can override the
touchesShouldBegin(_:with:in:)
,isPagingEnabled
, andtouchesShouldCancel(in:)
methods (which are called by the scroll view) to affect how the scroll view handles scrolling gestures你可以在继承的子类中重写这三个成员,从而控制客户在UIScrollView中的手势意图。
--While the gesture is in progress, the scroll view does not send any tracking calls to the subview.
当客户手势还在进行中的时候,UIScrollView不会发送任何“跟踪事件”给子视图。
--The
UIScrollView
class can have a delegate that must adopt theUIScrollViewDelegate
protocol. For zooming and panning to work, the delegate must implement bothviewForZooming(in:)
andscrollViewDidEndZooming(_:with:atScale:)
;★如果
UIScrollView需要实现平移和缩放功能,则必须接受
UIScrollViewDelegate
协议。也就是说,基本上你都要接受这个协议来实现代理咯。--If you assign a value to this view’s
restorationIdentifier
property, it attempts to preserve its scrolling-related information between app launches.如果你想再次启动app时,还保留着上一次的放大缩小平移状态的话,那就给UIScrollView的
restorationIdentifier
属性赋值吧。
1、UIScrollView常用属性
参考链接1:https://www.jianshu.com/p/2c74b7a6c082
参考链接2: https://www.jianshu.com/p/3385ef0805b8
首先必须有的一步设置代理对象 scrollView.delegate = self
UIScrollView是通过 代理对象delegate 来获取并且响应客户手势滑动的信息,也就是说用户的手势信息会通知到代理对象中,并进入到代理对象的某些方法中,也就是
UIScrollViewDelegate
协议 中声明的方法中。因此,UIScrollView有一个叫delegate的属性,你给这个属性赋值,也就设置了该UIScrollView的代理对象了。来看一下
UIScrollViewDelegate
协议中声明的常用的方法,你自己要用的话,就要自己实现,一般来讲,UIScrollView自己作为自己的代理。参考链接2: https://www.jianshu.com/p/3385ef0805b8func scrollViewDidScroll(_ scrollView: UIScrollView) { print("scrollView滚动时调用,只要offset的值发生变化就调用") } func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) { print("当将要开始缩放时,执行该方法。一次有效缩放就只执行一次。") } func scrollViewDidZoom(_ scrollView: UIScrollView) { print("当scrollView缩放时,调用该方法。在缩放过程中,会多次调用") } func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { print("当缩放结束后,并且缩放大小回到minimumZoomScale与maximumZoomScale之间后(我们也许会超出缩放范围),调用该方法。") } func viewForZooming(in scrollView: UIScrollView) -> UIView? { print("返回将要缩放的UIView对象。要执行多次") return scrollView.subviews.first } func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { print("指示当用户点击状态栏后,滚动视图是否能够滚动到顶部。")// 需要设置滚动视图的属性:scrollView.scrollsToTop=true return true } func scrollViewDidScrollToTop(_ scrollView: UIScrollView) { print("当滚动视图滚动到最顶端后,执行该方法") } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { print("当开始滚动视图时,执行该方法。一次有效滑动只执行一次。") } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { print("滑动视图,当手指离开屏幕那一霎那,调用该方法。一次有效滑动只执行一次。") } func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { print("滑动减速时调用该方法。") } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { print("滚动视图减速完成,滚动将停止时,调用该方法。一次有效滑动只执行一次。") } func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { print("当滚动视图动画完成后,调用该方法,如果没有动画,那么该方法将不被调用") } func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { print("滑动scrollView,并且手指离开时执行。一次有效滑动只执行一次。") //当pagingEnabled属性为true时,不调用该方法 }
◉ 属性:
contentOffset
偏移量,默认是CGPointZero,一般用在UIScrollView的代理方法里,用来做拖拽距离判断操作,用于判断当前手指滑动的距离contentOffset.x 或者 contentOffset.y
isDirectionalLockEnabled
默认为FALSE, 如果设置为TRUE,当你手指滑动完还在屏幕上的时候,会锁住水平或竖直方向的滑动。
bounces
弹性效果,默认是TRUE, 如果设置成false,则当你滑动到边缘时将不具有弹性效果
alwaysBounceVertical
竖直方向总是可以弹性滑动,(前提是属性bounces
必须为TRUE)
isPagingEnabled
是否可分页,默认是FALSE, 如果设置成TRUE, 则可分页
scrollIndicatorInsets
滑动条的内边距,即是距离上、左、下、右的距离
decelerationRate
减速率,CGFloat类型,当你滑动松开手指后的减速速率, 但是尽管decelerationRate
是一个CGFloat类型,但是目前系统只支持以下两种速率设置选择
isTracking
是get属性,客户触摸到屏幕后立即返回YES,即便还没开始滑动
isDragging
是get属性,手指滑动了才返回YES
isDecelerating
是get属性,手指离开屏幕了,视图仍然在变速滑动的时候返回YES
delaysContentTouches
延迟内容触摸,默认是true,作用就是可以延迟触发 “滑动视图上的” 子类控件(比如UIButton)的 响应事件,优先响应 UIScrollView的 事件;如果该属性设置成false,则会触发UIScrollView中的touchesShouldBegin(_ touches: Set<UITouch>, with event: UIEvent?, in view: UIView) -> Bool
方法
这个属性可以帮你了解很多东西,详细看一下咯:
首先假设你的scrollView上有一个subview是button
为了好看,你最好把button放置在scrollView的第二或者第三页比较好辨识一点。
首先翻译一下
delaysContentTouches
,就是延迟scrollView里面的内容被点击的事件啊,button是scrollView的内容啊。然后你的scrollview是直接用UIScrollView还是UIScrollView的子类又有区别,反正都是先假设有个button先,下面正题。
①直接用UIScrollView:
delaysContentTouches
属性设置为true,操作为:点击button并快速向上滑动;则子控件button的事件不被响应,而是只是响应了scrollView的拖曳事件,button被scrollView拖着移动。
delaysContentTouches
属性还是设置为true,操作为:长按button再向上滑动;则子控件button的事件被响应了,但是scrollView的拖曳事件不再被响应了,效果就是button没有向上移动。
delaysContentTouches
属性设置为false,操作为:点击button并快速向上滑动或者长按上滑;子控件肯定就被响应了啊,scrollView事件不被响应。这时候就涉及到深层一点的原理了,虽然我也不是很懂。UIScrollView内部会设置一个定时器,就用来统计客户点击子控件时的触碰时长的,它设置了一个阈值,如果手指在子控件上的停留时间大于这个阈值,那么UIScrolllView就会把这个触碰事件交付给子控件处理,如果低于这个阈值,则直接当做拖曳事件处理,不再交付给子控件。
②用UIScrollView的子类:
delaysContentTouches
属性默认设置为true,操作为:点击按钮立即上滑;和直接使用UIScrolllView时一样。
delaysContentTouches
属性还是设置为true,操作为:长按button再向上滑动;这时就不一样了。这里的子控件button的事件居然没有被响应了,响应的还是scrollView的拖曳事件。这是因为你继承UIScrolllView的时候,你一般要复写它的两个方法,touchesShouldBegin
和touchesShouldCancel
方法,如果这两个方法你都返回true的话,touchesShouldCancel
方法就将由button响应完的触摸事件给取消了,让button空欢喜一场。所以如果你复写的touchesShouldCancel
方法返回false的话,那么scrollView就会接受button返回来的触摸事件的响应,就相当于button响应了触摸事件。
delaysContentTouches
属性设置为false,操作为:点击button立即上滑、点击button长按上滑;这里有不一样了,button的事件都没有被响应,这里还是复写的touchesShouldCancel
方法搞的鬼,因为touchesShouldCancel
方法设置了true。还要注意,点击button的模式,是.touchUpInside
,还是touchUpOutside
模式,否则滑到button视图之外,会不被响应的。涉及到子控件时,scrollView会默认调用
touchesShouldBegin
和touchesShouldCancel
方法进行相应的事件判断。好了,到这里
delaysContentTouches
属性就说完了,要注意和scrollView 的 “touchesShouldCancel
方法“ 返回的bool值结合使用,但子控件是button时还要注意button的点击模式。
好了,继续说UIScrollView的属性:
canCancelContentTouches
可以取消”视图的内容“ 的触摸, 默认是true;true即可以优先响应UIScrollView事件;false即是在定时器够时长后,必须响应子视图,touchesShouldCancel
方法不会被调用。minimumZoomScale
滑动视图的最小缩放倍数,默认是1.0maximumZoomScale
滑动视图的最大缩放倍数,默认是1.0,自定义时肯定要大于minimumZoomScale啊。
注意:需要实现缩放效果,scrollView的 “代理delgate” 必须要实现
func viewForZooming(in scrollView: UIScrollView) -> UIView?
方法
zoomScale
当前的缩放比例, 默认是1.0bouncesZoom
弹性缩放,默认是true, 设置成false的话,当缩放到最大或最小值的时候不会有弹性效果isZooming
正在缩放,get属性,正在进行缩放的时候是true, 松开就是falseisZoomBouncing
是 get属性,Bool值,用于判断是否缩放到了边界(最小最大值0)。弹性超出的最大值视作“没到”边界scrollsToTop
滑动到顶部,默认是true,当点击状态栏的时候滑动到顶部;注意:它的delegate方法
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool
不能返回false
,否则没用。
panGestureRecognizer
平移手势,get属性,可以通过设置平移手势的属性来改变平移方式,比如设置触摸手指的最少个数minimumNumberOfTouches,它是一个
UIPanGestureRecognizer类对象,pan是平移的意思
pinchGestureRecognizer
捏合手势,也就是缩放手势,get属性,设置同平移手势一样,当缩放禁用时返回nil。pinch是挤捏的意思。
keyboardDismissMode
键盘消失模式, 默认是none,是个枚举值
refreshControl
这是UIScrollView自带的刷新控件(iOS 10.0以后才有的,很少用到)接下来看UIScrollView里的方法:
◉ 方法:
setContentOffset(_ contentOffset: CGPoint, animated: Bool)
设置内容滑动的偏移量(动画),你可以在别的地方点击触发,也就是在别的地方调用此方法scrollRectToVisible(_ rect: CGRect, animated: Bool)
滑动到指定的可见区域(带动画),就是将内容滑动到你指定的某个坐标位置的CGRect所组成的矩形区域,使同处于该坐标位置的内容视图可见flashScrollIndicators()
滑动条闪烁,当页面加载成功出现时,滑动条会自动显示出来,停留一下又自动隐藏,就这么一下效果。touchesShouldBegin(_ touches: Set<UITouch>, with event: UIEvent?, in view: UIView) -> Bool
触摸事件开始,意思是开始响应UIScrollView的子视图(比如button)的事件,这个方法必须要子类化UIScrollView,即复写父类才可以起到作用。在触摸事件传递到UIScrollView的子视图之前就会被自动调用touchesShouldCancel(in view: UIView) -> Bool
触摸事件取消,返回true直接取消UIScrollView的子视图的响应,直接由UIScrollView响应;在触摸事件从子视图返回传递到UIScrollView时就会被自动调用。func setZoomScale(_ scale: CGFloat, animated: Bool)
设置当前的缩放比例(可带动画)func zoom(to rect: CGRect, animated: Bool)
把整个内容视图缩放到指定的区域,缩放比例自己用内容视图与CGRect的宽高比来算,取较小者。
◉ 接着看的是代理UIScrollViewDelegate协议的方法:
func scrollViewDidScroll(_ scrollView: UIScrollView)
当内容视图已经滑动时,自动调用此方法。func scrollViewDidZoom(_ scrollView: UIScrollView)
当内容视图已经缩放时,自动调用此方法func scrollViewWillBeginDragging(_ scrollView: UIScrollView)
即当你的手指刚开始拖拽,自动调用此方法,在代理协议里的方法基本上都是系统自动调用的,所以你自己可以去实现。func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
即将松开手指结束拖拽,还没松开func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool)
已经松开手指,结束拖拽状态func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView)
即将开始减速func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
已经结束减速, 即当UIScrollView滑动停止时就调用此方法func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView)
已经结束滑动动画,这个方法起作用的前提是设置了上面提到的两种方法中的任意一种,否则不会起作用!func viewForZooming(in scrollView: UIScrollView) -> UIView?
返回一个需要缩放的视图,需要做缩放的时候必须调用此方法func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?)
即将开始缩放,在滑动视图开始缩放它的内容视图前调用func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat)
已经结束缩放状态,结束缩放手势时调用,在最小和最大值之前进行缩放func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool
滑到顶部,这个方法和属性scrollsToTop
用法一致,本质一致,返回true则是可以滑动顶部,false反之!func scrollViewDidScrollToTop(_ scrollView: UIScrollView)
已经滑到顶部,当滑动到顶部的动画完成的时候调用
官网的API文档
Topics --专题
Responding to Scroll View Interactions --响应与Scroll View的交互
var delegate: UIScrollViewDelegate?
--属性:这里赋值遵循代理协议的对象
--The delegate of the scroll-view object.
protocol UIScrollViewDelegate
--代理协议介绍
--The methods declared by the UIScrollViewDelegate
protocol allow the adopting delegate to respond to messages from the UIScrollView
class and thus respond to, and in some affect, operations such as scrolling, zooming, deceleration of scrolled content, and scrolling animations.
该协议主要是响应滑动,缩放,滑动视图内容的声明,滑动的动画效果。
Managing the Content Size and Offset --管理内容视图的尺寸和位移
var contentSize: CGSize
--属性:视图内容的尺寸
--The size of the content view.
var contentOffset: CGPoint
--属性:视图内容的偏移量
--The point at which the origin of the content view is offset from the origin of the scroll view.
func setContentOffset(CGPoint, animated: Bool)
--方法:设置视图内容的偏移
--Sets the offset from the content view’s origin that corresponds to the receiver’s origin.
Managing the Content Inset Behavior --管理内容的内边距行为
var adjustedContentInset: UIEdgeInsets
--get属性:获取scrollView的视图内容和安全区的内边距
--The insets derived from the content insets and the safe area of the scroll view.
var contentInset: UIEdgeInsets
--属性:视图内容在安全区或view中的内边距
--The custom distance that the content view is inset from the safe area or scroll view edges.
var contentInsetAdjustmentBehavior: UIScrollView.ContentInsetAdjustmentBehavior
--枚举属性:确定视图内容的内边距的调整方式
--The behavior for determining the adjusted content offsets.
enum UIScrollView.ContentInsetAdjustmentBehavior
--枚举常量:内边距的调整方式
--Constants indicating how safe area insets are added to the adjusted content inset.
func adjustedContentInsetDidChange()
--方法:内边距调整后,自动调用
--Called when the adjusted content insets of the scroll view change.
Getting the Layout Guides --获取布局参考线
var frameLayoutGuide: UILayoutGuide
--get属性:获取frame未发生形变时的布局参考对象
--The layout guide based on the untransformed frame rectangle of the scroll view.
var contentLayoutGuide: UILayoutGuide
--get属性:获取视图内容未发生形变时的布局参考对象
--The layout guide based on the untranslated content rectangle of the scroll view.
Configuring the Scroll View --配置scrollView功能,页面元素
var isScrollEnabled: Bool
--Bool属性:断定是否可滑动
--A Boolean value that determines whether scrolling is enabled.
var isDirectionalLockEnabled: Bool
--Bool属性:断定是否锁定滑动方向
--A Boolean value that determines whether scrolling is disabled in a particular direction.
var isPagingEnabled: Bool
--Bool属性:断定滑动暂停时是否分页的形式
--A Boolean value that determines whether paging is enabled for the scroll view.
var scrollsToTop: Bool
--Bool属性:断定点击状态栏时是否滑动到顶部
--A Boolean value that controls whether the scroll-to-top gesture is enabled.
var bounces: Bool
--Bool属性:断定滑动到边缘时是否要回弹效果
--A Boolean value that controls whether the scroll view bounces past the edge of content and back again.
var alwaysBounceVertical: Bool
--Bool属性:断定垂直方向滑动到边缘时是否要回弹效果
--A Boolean value that determines whether bouncing always occurs when vertical scrolling reaches the end of the content.
var alwaysBounceHorizontal: Bool
--Bool属性:断定水平方向滑动到边缘时是否要回弹效果
--A Boolean value that determines whether bouncing always occurs when horizontal scrolling reaches the end of the content view.
Getting the Scrolling State --获取滑动的状态
var isTracking: Bool
--get属性:只要用户碰到屏幕就立马返回true
--Returns whether the user has touched the content to initiate scrolling.
var isDragging: Bool
--get属性:手指滑动一定的时候和距离时就返回
A Boolean value that indicates whether the user has begun scrolling the content.
Returns whether the content is moving in the scroll view after the user lifted their finger.
var decelerationRate: UIScrollView.DecelerationRate
A floating-point value that determines the rate of deceleration after the user lifts their finger.
struct UIScrollView.DecelerationRate
Deceleration rates for the scroll view.
Managing the Scroll Indicator and Refresh Control --管理滑动指示器和刷新控件
var indicatorStyle: UIScrollView.IndicatorStyle
The style of the scroll indicators.
enum UIScrollView.IndicatorStyle
The style of the scroll indicators. You use these constants to set the value of the indicatorStyle
style.
var scrollIndicatorInsets: UIEdgeInsets
The distance the scroll indicators are inset from the edge of the scroll view.
Deprecatedvar showsHorizontalScrollIndicator: Bool
A Boolean value that controls whether the horizontal scroll indicator is visible.
var showsVerticalScrollIndicator: Bool
A Boolean value that controls whether the vertical scroll indicator is visible.
Displays the scroll indicators momentarily.
var refreshControl: UIRefreshControl?
The refresh control associated with the scroll view.
Scrolling to a Specific Location --滑动到指定位置
func scrollRectToVisible(CGRect, animated: Bool)
Scrolls a specific area of the content so that it is visible in the receiver.
Managing Touches --管理触摸事件
func touchesShouldBegin(Set<UITouch>, with: UIEvent?, in: UIView) -> Bool
Overridden by subclasses to customize the default behavior when a finger touches down in displayed content.
func touchesShouldCancel(in: UIView) -> Bool
Returns whether to cancel touches related to the content subview and start dragging.
var canCancelContentTouches: Bool
A Boolean value that controls whether touches in the content view always lead to tracking.
var delaysContentTouches: Bool
A Boolean value that determines whether the scroll view delays the handling of touch-down gestures.
var directionalPressGestureRecognizer: UIGestureRecognizer
The underlying gesture recognizer for directional button presses.
Zooming and Panning --缩放与平移
var panGestureRecognizer: UIPanGestureRecognizer
The underlying gesture recognizer for pan gestures.
var pinchGestureRecognizer: UIPinchGestureRecognizer?
The underlying gesture recognizer for pinch gestures.
func zoom(to: CGRect, animated: Bool)
Zooms to a specific area of the content so that it is visible in the receiver.
A floating-point value that specifies the current scale factor applied to the scroll view's content.
func setZoomScale(CGFloat, animated: Bool)
A floating-point value that specifies the current zoom scale.
A floating-point value that specifies the maximum scale factor that can be applied to the scroll view's content.
A floating-point value that specifies the minimum scale factor that can be applied to the scroll view's content.
A Boolean value that indicates that zooming has exceeded the scaling limits specified for the receiver.
A Boolean value that indicates whether the content view is currently zooming in or out.
A Boolean value that determines whether the scroll view animates the content scaling when the scaling exceeds the maximum or minimum limits.
Managing the Keyboard --管理键盘
var keyboardDismissMode: UIScrollView.KeyboardDismissMode
The manner in which the keyboard is dismissed when a drag begins in the scroll view.
enum UIScrollView.KeyboardDismissMode
The manner in which the keyboard is dismissed when a drag begins in the scroll view.
Managing the Index --管理索引
var indexDisplayMode: UIScrollView.IndexDisplayMode
The manner in which the index is shown while the user is scrolling.
enum UIScrollView.IndexDisplayMode
The manner in which the index is shown while the user is scrolling.
Instance Properties --实例属性
var automaticallyAdjustsScrollIndicatorInsets: Bool
var horizontalScrollIndicatorInsets: UIEdgeInsets
var verticalScrollIndicatorInsets: UIEdgeInsets