Hello, World!

UIScrollView、UITableView实现机制

字数统计: 618阅读时长: 2 min
2018/12/07 Share

UIScrollView

UIScrollView继承自UIView,它的作用是通过滑动来扩展边界。

分析

我们知道UIViewbounds属性的作用是以自身为参考坐标系,来描述自己的大小和位置。如下如所示:

bounds

通过上图可知,修改控件自身bounds属性的origin值,内部的子控件的位置就会发生相应的变化。

因为UIScrollView继承自UIView,所以它是利用了bounds属性的特点,来实现滑动的特性的。

实现原理

通过模拟系统UIScrollView,实现自定义的CustomScrollView控件,来了解UIScrollView的底层实现原理。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@interface CustomScrollView : UIView

@property (nonatomic, assign) CGSize contentSize;

@end

@implementation CustomScrollView

- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGes:)];
[self addGestureRecognizer:pan];
self.clipsToBounds = YES;
}
return self;
}

- (void)panGes:(UIPanGestureRecognizer *)panGes {

CGRect tmpBound = self.bounds;

CGPoint p = [panGes translationInView:self];

// 设置ContentSize
{
CGFloat minimumOffset = 0.f;
CGFloat maximumOffset = _contentSize.width - self.bounds.size.width;
CGFloat actualOffset = fmax(minimumOffset, fmin(maximumOffset, (self.bounds.origin.x - p.x)));
tmpBound.origin.x = actualOffset;
}
{
CGFloat minimumOffset = 0.f;
CGFloat maximumOffset = _contentSize.height - self.bounds.size.height;
CGFloat actualOffset = fmax(minimumOffset, fmin(maximumOffset, (self.bounds.origin.y - p.y)));
tmpBound.origin.y = actualOffset;
}

[panGes setTranslation:CGPointZero inView:self];

self.bounds = tmpBound;

}

@end

根据以上代码可得出控件滚动是因为bounds.origin值的不断修改。

滑动原理:UIScrollView通过自身的Pan拖拽手势,可以获取手指移动的方向和距离,然后根据移动的方向和距离再去修改自身的bounds.origin值。

contentInset

contentInset的作用是扩展内容区域。

1
2
3
4
5
6
scrollView.contentInset = UIEdgeInsetsMake(88.f, 0.f, 0.f, 0.f);

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// ScrollView实质就是修改它的bounds来进行view的滑动,可以在代理方法里打印ScrollView的bounds值来看
NSLog(@"scrollView bounds %@", NSStringFromCGRect(scrollView.bounds));
}

打印的信息:scrollView bounds {{0, -88}, {375, 667}}

根据打印的信息可知contentInset的实现原理是修改bounds.origin值。

从iOS11开始废弃了控制器的 automaticallyAdjustsScrollViewInsets属性。使用 UIScrollView的contentInsetAdjustmentBehavior属性替代。

  1. automaticallyAdjustsScrollViewInsets = YES的时候,内容区域向下偏移 导航栏高度 + 状态栏高度 的距离。此时 scrollView.contentInset = {64, 0, 0, 0}scrollView.contentOffset = {0, -64}

  2. contentInsetAdjustmentBehavior = Automatic/Always的时候,内容区域向下偏移 导航栏高度 + 状态栏高度 的距离。此时 非刘海屏 scrollView.contentInset = {0, 0, 0, 0}scrollView.contentOffset = {0, -64}scrollView.adjustedContentInset = {64, 0, 0, 0}, 刘海屏 scrollView.contentInset = {0, 0, 0, 0}scrollView.contentOffset = {0, -88}scrollView.adjustedContentInset = {88, 0, 34, 0}

UITableView

UITableView继承自UIScrollView,它通过列表来展示信息。

CATALOG
  1. 1. UIScrollView
    1. 1.1. 分析
    2. 1.2. 实现原理
    3. 1.3. contentInset
  2. 2. UITableView