1 ScrollView实质
- scrollView.contentView才是其展示内容,对应的是contentSize,需要手动设置(类似frame)
- 在滑动时,实际改变的是scrollView.bounds.origin。
bounds是scrollView内部坐标系,bounds
的origin
,相当于原点的坐标值
。
2 contentOffset
拉列表,列表越长,contentOffset越大 contentSize、contentOffset和contentInset的图解辨别
Banner于TableView
- 可以放在Header中
- 也可以作为子控件,然后设置contentOffset.y=-50。
3 一些属性
3.1 自动安全区域偏移相关
-
adjustContentInset
,表示contentView.frame.origin偏移了scrollview.frame.origin多少;是系统计算得来的,计算方式由contentInsetAdjustmentBehavior
决定。计算规则较复杂,iOS 11 安全区域适配总结
不希望上面Inset中自动修改,iOS11之前设置Controller的automaticallyAdjustsScrollViewInsets为NO,iOS11之后
if ([scrollview respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
scrollview.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
复制代码
- contentInset、scrollIndicatorInsets
作为controller.view
的第0个子视图
时,会自动修改contentInset和scrollIndicatorInsets属性,避免导航栏、TabBar的遮挡。
3.2 其他
- bounces
默认YES,即使用户滑到了头,还是可以滑(可用于下拉刷新等),会显示背景色。
- alwaysBounceVertical
依赖于bounces=YES
默认NO,设置成YES,即使内容小于bounds,也可以拉。
- pagingEnabled
只能停止在bounds的倍数上,配合UIPageControl可以实现轮播页的效果
- zoomScale相关
放缩用
- dragging, tracking, decelerating
状态相关,拖曳、触碰、减速状态
4 一些应用
4.1 悬停控件
原理:通过-scrollViewDidScroll:代理方法,跟踪contentOffset的的变化。不满足悬停条件时,hidden。
4.2 头部图片下拉放大
原理:
通过-scrollViewDidScroll:代理方法跟踪contentOffset的的变化,根据contentOffset动态设置图片的缩放比例
// 以"动态修改图片缩放比例于1倍和2倍之间"为例
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat scale = 1 - (scrollView.contentOffset.y / 100);
scale = (scale >= 1) ? scale : 1;
scale = (scale <= 2) ? scale : 2;
imageView.transform = CGAffineTransformMakeScale(scale, scale);
}
复制代码
4.3 无限轮播
原理:
创建N+2个UIImageView
通过-scrollViewDidScroll:代理方法,跟踪contentOffset的的变化
在滑动到首尾两个图片处,设置contentOffset到真实图片处。
4.4 图片浏览器+图片放缩功能】
苹果相册、微信朋友圈9图
原理:
N个图片UIImageView
- 里面可以先放尺寸放大的缩略图。
- 放缩【?】
4.5 其他
- 判断滑动方向
根据精度需求,在代理方法中contentOffset.y。(TableView可以根据cellForRowAtIndex)
5 常用代理方法
滚动
//contentOffset发生变化时调用(包括拖曳、减速过程、直接代码设置)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
复制代码
手指相关,一次拖曳,只调用一次。
//1 将要开始拖拽页面(非实时)
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
//2 将要停止拖曳时(以velocity为初速度,直到targetContentOffset停止。可通过修改targetContentOffset,调整位置)
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
//3 手指离开时,如果decelerate为NO时,表示`滑动动画结束`。
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView;
//4 判断停止动画,要配合2或3,才完备
- (void)scrollViewWillEndDecelerating:(UIScrollView *)scrollView;
复制代码
放缩
- (void)scrollViewDidZoom:(UIScrollView *)scrollView;
- (nullable UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; // return a view that will be scaled. if delegate returns nil, nothing happens
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view NS_AVAILABLE_IOS(3_2); // called before the scroll view begins zooming its content
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(nullable UIView *)view atScale:(CGFloat)scale;
复制代码
状态栏
// 当scrollView已经滑动到顶部时调用(仅当点击状态栏让scrollView滑动到顶部才调用)
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView;
复制代码
// 当-setContentOffset:animated:/-scrollRectVisible:animated:方法动画结束时调用(仅当animated设置为YES时才调用)
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView;
复制代码
6 其他值得注意的地方
UIScrollView处理触摸事件原理
用户按下时,UIScrollView不知道用户的意图
在按下一瞬间,UIEvent传递完成后,不会立刻响应,而是开始一个150ms的倒计时,并监听用户接下来的行为
- 倒计时结束前,手指发生移动,滚动contentView,优先识别为手势。
- 倒计时结束时,手指位置没改变,调用-touchesShouldBegin:withEvent:inContentView: 询问是否将事件的响应权交给子视图(NO则不传递,YES则传递,默认YES)
- 事件传递给子视图后,手指位置又发生了移动,调用-touchesShouldCancelInContentView:询问是否取消已传递给子视图的事件
这里原文中写的是,ScrollView会拦截UIEvent不传给子视图,根据经验,如果ScrollView嵌套ScrollView,还是子ScrollView先响应,因此,传递应该还是会传递的,但是谁来响应,依据是手势 & 响应链中说的,手势>UIControl>UIResponder。
UIScrollView的详细使用介绍和实现原理分析[2018.06.20更新]
UIScrollView实战经验
只能横向/纵向滑动
只允许横向:
设置subview的frameY与scrollView的frameY相等,然后设置alwaysBounceVertical=No(默认就是No)