首先在聊UIScrollView之前,我们先了解坐标系在 UIKit 中是如何工作的。
坐标系
因为每个视图定义了自己的坐标系,它看起来这像是这样的:x 轴指向右侧、y 轴指向下方(如下图所示)。
![](https://img.haomeiwen.com/i1928303/7e86ae11dcdd1884.png)
但是需要注意的是此逻辑坐标系与视图的宽度和高度无关。它没有边界,在四个方向上无限延伸(无限延伸不是真的无限,因为坐标系的范围受CGFloat数据类型大小的限制32 位系统上为 32 位,64 位上为 64 位)。现在让我们在坐标系中布置一些视图。每个不同颜色矩形代表一个子视图:
![](https://img.haomeiwen.com/i1928303/8f18262de5db359d.png)
实际的代码如下:
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)];
redView.backgroundColor = [UIColor colorWithRed:0.815 green:0.007
blue:0.105 alpha:1];
UIView *greenView = [[UIView alloc] initWithFrame:CGRectMake(150, 160, 150, 200)];
greenView.backgroundColor = [UIColor colorWithRed:0.494 green:0.827
blue:0.129 alpha:1];
UIView *blueView = [[UIView alloc] initWithFrame:CGRectMake(40, 400, 200, 150)];
blueView.backgroundColor = [UIColor colorWithRed:0.29 green:0.564
blue:0.886 alpha:1];
UIView *yellowView = [[UIView alloc] initWithFrame:CGRectMake(100, 600, 180, 150)];
yellowView.backgroundColor = [UIColor colorWithRed:0.972 green:0.905
blue:0.109 alpha:1];
[mainView addSubview:redView];
[mainView addSubview:greenView];
[mainView addSubview:blueView];
[mainView addSubview:yellowView];
说到视图就要聊到bounds和frame这个概念,接下来简单的说说bounds和frame。
Bounds
对于apple里面对于view的bounds解释如下:
边界矩形,它描述了视图在其自己的坐标系中的位置和大小。
视图可以被认为是进入由其坐标系定义的平面矩形区域的窗口或视口。并且视图bounds表达了这个矩形的位置和大小。
假设我们视图的bounds矩形的宽度和高度为 320 x 480 points,并且它的原点是默认值(0, 0)。视图成为坐标系平面的视口,显示整个平面的一小部分。界外的一切都还在,只是隐藏了。实际上,除非clipsToBounds == YES(默认为NO),边界矩形外的子视图将保持可见。但是,视图不会检测到超出其边界的触摸。
![](https://img.haomeiwen.com/i1928303/f44bcf2d1e3a7460.png)
接下来,我们将修改边界矩形的原点:
CGRect bounds = mainView.bounds;
bounds.origin = CGPointMake(0, 100);
mainView.bounds = bounds;
边界矩形的原点现在位于(0, 100)
所以我们的场景看起来像这样:
![](https://img.haomeiwen.com/i1928303/1d3e7a20a8fd9439.png)
看起来好像视图向下移动了 100 点,实际上这与它自己的坐标系有关。视图在屏幕上的实际位置(更准确地说在其父视图中)保持固定,没有发生改变,因为这是由它的frame确定的。
上面的图片坐标都是针对自身视图画的坐标系。
因为视图的位置是固定的(从它自己的角度来看),把坐标系平面想象成一块我们可以拖动的画布,把视图想象成一块透明的玻璃。调整bounds的原点相当于移动画布,使其另一部分通过视图可见。这样就可以变成如下图所示的一样:
![](https://img.haomeiwen.com/i1928303/fef78a9c286a546a.png)
这正是UIScrollView它滚动时的原理。但是从用户感知中似乎是视图的子视图在移动,尽管它们在视图坐标系(换句话说,它们的框架)方面的位置保持不变。而我们也可以通过这样的原理来自己写一个简单的UIScrollView出来(因为系统的UIScrollView来牵扯到动量滚动、弹跳、滚动指示器、缩放和委托方法等)。
自定义UIScrollView
滚动视图不需要不断更新其子视图的坐标以使其滚动。它所要做的就是调整其边界的原点。这样实现一个非常简单的滚动视图就变得微不足道了。我们设置了一个手势识别器来检测用户的平移手势,为了响应手势,我们bounds通过拖动量来平移视图:
// CustomScrollView.h
@import UIKit;
@interface CustomScrollView : UIView
@property (nonatomic) CGSize contentSize;
@end
// CustomScrollView.m
#import "CustomScrollView.h"
@implementation CustomScrollView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self == nil) {
return nil;
}
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(handlePanGesture:)];
[self addGestureRecognizer:gestureRecognizer];
return self;
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer
{
CGPoint translation = [gestureRecognizer translationInView:self];
CGRect bounds = self.bounds;
// Translate the view's bounds, but do not permit values that would violate contentSize
CGFloat newBoundsOriginX = bounds.origin.x - translation.x;
CGFloat minBoundsOriginX = 0.0;
CGFloat maxBoundsOriginX = self.contentSize.width - bounds.size.width;
bounds.origin.x = fmax(minBoundsOriginX, fmin(newBoundsOriginX, maxBoundsOriginX));
CGFloat newBoundsOriginY = bounds.origin.y - translation.y;
CGFloat minBoundsOriginY = 0.0;
CGFloat maxBoundsOriginY = self.contentSize.height - bounds.size.height;
bounds.origin.y = fmax(minBoundsOriginY, fmin(newBoundsOriginY, maxBoundsOriginY));
self.bounds = bounds;
[gestureRecognizer setTranslation:CGPointZero inView:self];
}
@end
就像真实的一样UIScrollView,我们的类有一个contentSize必须从外部设置的属性来定义可滚动区域的范围。当我们调整边界时,我们确保只允许有效值。这样的一个运行效果如下:
![](https://img.haomeiwen.com/i1928303/94f5af687595f0fd.png)
Frame
框架矩形,它描述了视图在其父视图坐标系中的位置和大小。
因为本文是针对UIScrollView的了解,对于Frame 以及Frame与Bounds的区别将在下篇详细的介绍,这里就一笔带过。
网友评论