使用场景

在 iOS 中当屏幕装不下需要显示的内容时,就需要使用 Scroll views。使用Scroll views 有两个主要目的:

  • 通过拖拽 Scroll views 的内容区域,让屏幕外的内容显示出来
  • 使用捏合手势(pinch gestures)让内容放大或者缩小

概览

UIScrollView 类有这几个功能:

  • 滑动 scroll view 中的内容
  • 缩放内容,通过手势放大缩小内容
  • 翻页模式

UIScrollView的所有操作都是针对其subviews。

基础UIScrollView

实现一个最基础只有滑动功能的 UIScrollView 非常简单,必须步骤只有两个:

  1. 设置 UIScrollView 的 contentSize 属性。这个属性代表可以滚动的区域大小。
  2. 在 UIScrollView 中添加子 view。子 view 提供显示的内容。

还有一些可选项来完善 UIScrollView 比如 vertical and horizontal scroll indicators, drag bouncing, zoom bouncing, and directional constraint of scrolling。

在 UIScrollView 中添加一张很大的图片,然后实现拖动 UIScrollView 显示图片内容:

1
2
3
4
5
6
7
8
9
10
11
- (void)viewDidLoad {
  [super viewDidLoad];
  UIImage *image = [UIImage imageNamed:@"test.jpg"];
  UIImageView *imageView = [[UIImageView alloc] initWithImage:image];

  // 1. 设置 UIScrollView 的 contentSize 属性。这个属性代表可以滚动的区域大小。
  self.scrollView.contentSize = imageView.bounds.size;

  // 2. 在 UIScrollView 中添加子 view。子 view 提供显示的内容。
  [self.scrollView addSubview:imageView];
}

设置contentInset,bouncing,滑动方向等都比较简单就略过之…


需要注意的是:

在设置UIScrollView的contentInset的时候,UIScrollView 的滚动条会变得很奇怪。
如果想让滚动条能正常显示应该同时设置scrollIndicatorInsets。

如:
self.scrollView.contentInset = UIEdgeInsetsMake(100, 0, 0, 0);
self.scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(100, 0, 0, 0);

不用手势让 UIScrollView 滚起来

我们可以:

  1. setContentOffset:animated:

    • 让 UIScrollView 滚到指定的偏移值(contentOffset),contentOffset 是一个 CGPoint,其中的 x,y 是屏幕左上角的那个点在 UIScrollView 的 content 中的坐标

    • 如果 animated 传入的是 YES,UIScrollVIew 的 delegate 的scrollViewDidScroll:scrollViewDidEndScrollingAnimation:方法都会收到消息。如果 animated 传入的是 NO,只有scrollViewDidScroll:方法会收到消息。

  2. scrollRectToVisible:animated:

    • 让 UIScrollView 滚动到指定的矩形区域可见位置
    • animated传入值对 delegate 的影响同上
  3. 点击屏幕状态栏 UIScrollView Scroll to top

    可以在 delegate 的 scrollViewShouldScrollToTop:方法中设置是否开启这个功能。

UIScrollViewDelegate

UIScrollView 状态

当UIScrollView 正在滑动的时候,以下几个状态属性会发生改变,UIScrollView 也正是通过这些状态来判断要让 delegate 发出什么消息。

表 1-1:UIScrollView 状态属性

State property Description
tracking YES if the user’s finger is in contact with the device screen.
dragging YES if the user’s finger is in contact with the device screen and has moved.
decelerating YES if the scroll view is decelerating as a result of a flick gesture, or a bounce from dragging beyond the scroll view frame.
zooming YES if the scroll view is tracking a pinch gesture to change its zoomScale property..
contentOffset A CGPoint value that defines the top-left corner of the scroll view bounds.

Delegate 方法的生命周期

  1. tracking = YES
  2. dragging = YESscrollViewWillBeginDragging:
  3. 改变contentOffsetscrollViewDidScroll:
  4. 手指离开 UIScrollView,tracking = YESscrollViewDidEndDragging:willDecelerate:
  5. 如果decelerate = YES,发送scrollViewWillBeginDecelerating:。否则直接到第6步
  6. decelerating = NO,scrollViewDidEndDecelerating:,所有步骤完成!

知道了 delegate 在什么时候发送什么消息,我们就可以根据实际需求在特定的方法里写代码了。关于 UIScrollViewDelegate 的方法名表达的意思非常清楚。还有一些 delegate 方法是关于缩放(zoom)的生命周期的,其实原理和上面一样。只是第三步的scrollViewDidScroll:改成scrollViewDidZoom

支持放大缩小

UIScrollView支持捏合手势放大缩小的关键:指定一个 View 作为放大缩小的对象。

1
2
3
4
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return self.imageView;
}

可以看出,每次放大缩小只有一个 view 会被放大缩小,如果只是放大缩小图片,使用 UIImageView 就可以了。如果需要缩放多个 view,就需要一个用一个 view的容器将这些 views 全包含进去。

支持 pageMode

设置 pageMode = YES 就可以看到一页一页滚动的效果,但是 pageMode 是在特定的场景下使用,并且代码也不只是pageMode = YES就完了。Apple 官方有一个非常好的代码例子:

PageControl sample code

UIScrollView 与 Autolayout

在使用 autolayout 的情况下由于各个 View 之间的距离都是通过 NSLayoutConstraint 设置,这样就算改变 父 view 的 bounds.orign,子 view 也不会改变位置。

官方提供了两种方法让 UIScrolView 在 autolayout 下正常滚动:

1. 将所有的子 view 嵌套在一个 UIView 中,让 UIScrollView 滚动这个 子 view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)viewDidLoad {
  UIView *contentView;

  contentView = [[UIView alloc] initWithFrame:CGRectMake(0,0,contentWidth,contentHeight)];

  [scrollView addSubview:contentView];

  // DON'T change contentView's translatesAutoresizingMaskIntoConstraints,
  // which defaults to YES;

  // Set the content size of the scroll view to match the size of the content view:
  [scrollView setContentSize:CGMakeSize(contentWidth,contentHeight)];

   /* the rest of your code here... */

}
  

2.纯 Autolayout 方式

To use the pure autolayout approach do the following:

  • Set translatesAutoresizingMaskIntoConstraints to NO on all views involved.
  • Position and size your scroll view with constraints external to the scroll view.
  • Use constraints to lay out the subviews within the scroll view, being sure that the constraints tie to all four edges of the scroll view and do not rely on the scroll view to get their size.

A simple example would be a large image view, which has an intrinsic content size derived from the size of the image. In the viewDidLoad method of your view controller, you would include code similar to the code shown in the listing below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (void)viewDidLoad {
    UIScrollView *scrollView;
    UIImageView *imageView;
    NSDictionary *viewsDictionary;

    // Create the scroll view and the image view.
    scrollView  = [[UIScrollView alloc] init];
    imageView = [[UIImageView alloc] init];

    // Add an image to the image view.
    [imageView setImage:[UIImage imageNamed:"MyReallyBigImage"]];

    // Add the scroll view to our view.
    [self.view addSubview:scrollView];

    // Add the image view to the scroll view.
    [scrollView addSubview:imageView];

    // Set the translatesAutoresizingMaskIntoConstraints to NO so that the views autoresizing mask is not translated into auto layout constraints.
    scrollView.translatesAutoresizingMaskIntoConstraints  = NO;
    imageView.translatesAutoresizingMaskIntoConstraints = NO;

    // Set the constraints for the scroll view and the image view.
    viewsDictionary = NSDictionaryOfVariableBindings(scrollView, imageView);
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics: 0 viewsDictionary:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics: 0 viewsDictionary:viewsDictionary]];
    [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[imageView]|" options:0 metrics: 0 viewsDictionary:viewsDictionary]];
    [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView]|" options:0 metrics: 0 viewsDictionary:viewsDictionary]];

    /* the rest of your code here... */
}

对 UIScrollView 的 content 的理解:

  • 滚动、缩放都是相对于 ScrollView 中的内容的,相当于 ScrollView 是一个窗口,里面的内容被各种缩放、滚动改变位置,我们看到的窗口所在的位置没有发生改变,改变的只是里面的内容。
  • UIScrollView 中的缩放是缩放 contentSize
  • 设置 UIScrollView 中子 View 的 frame 时,是相对于 UIScrollView 的 frame 的,和 contentSize 无关。

Reference

iOS UITextView 输入内容实时更新 cell 的高度

这篇文章介绍了在一个动态数据的 table view 中,cell 根据 text view 内容的输入实时改变 cell 和 table view 的高度。自动计算 cell 高度的功能使用 iOS 8 才支持的自适应 cell,如果你还不知道 iOS 8 自适应 cell, …… Continue reading

iOS 8 自适应 Cell

Published on November 13, 2014