1 ScrollView实质

  1. scrollView.contentView才是其展示内容,对应的是contentSize,需要手动设置(类似frame)
  2. 在滑动时,实际改变的是scrollView.bounds.origin。

bounds是scrollView内部坐标系,boundsorigin,相当于原点的坐标值




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

  1. 里面可以先放尺寸放大的缩略图。
  2. 放缩【?】

4.5 其他

  1. 判断滑动方向

根据精度需求,在代理方法中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)