直到iOS 8之前,在UIView的生命周期中,layoutSubviews方法始终都是一个重要的方法。随着Auto Layout的发展,这种方法已经不再像过去那样常用,但在某些情况下,我们仍然需要使用它来布局我们的子视图。那么,如何正确地利用layoutSubviews方法来布局子视图呢?在本文中,我们将一起探讨这个问题。
1. 什么是layoutSubviews方法?
layoutSubviews方法是UIView生命周期中的一个方法,它是在UIView本身的布局发生更改时自动调用的。这意味着,当我们改变一个UIView的frame属性时,该UIView的layoutSubviews方法会被自动调用。
那么什么情况下会触发UIView的layoutSubviews方法呢?除了改变frame属性以外,还有一些其他情况。比如:
- 当我们调用了setNeedsLayout方法时,该UIView的layoutSubviews方法会被自动调用。
- 当我们在某个子视图中添加或者删除子视图时,父视图的layoutSubviews方法会被自动调用。
- 当我们改变父视图的bounds属性时,父视图的layoutSubviews方法会被自动调用。
2. 如何调用layoutSubviews方法?
正如我们在上一节中所提到的,layoutSubviews方法是自动调用的。我们只需要遵循一些规则,就能够让它正确地工作。那么这些规则是什么呢?
- 我们不能显式地调用layoutSubviews方法。也就是说,我们不能直接调用[self layoutSubviews]方法。如果我们这样做的话,可能会导致循环调用,从而导致崩溃。
- 我们应该在UIView的子类中重写layoutSubviews方法,并在其中布局我们的子视图。
重要的是要记住,当我们重写layoutSubviews方法时,我们也应该调用[super layoutSubviews]方法。这是因为这个方法可能会执行一些UIView的默认布局,我们不希望破坏这些默认布局。
3. 如何布局子视图?
现在我们已经知道了layoutSubviews方法的基础知识,接下来让我们一起来看一看如何利用它来布局子视图。
实际上,我们可以利用layoutSubviews方法来实现各种不同的布局方式。下面是其中一些常见的方法。
3.1 手动布局
手动布局是最基本的一种布局方式。我们在layoutSubviews方法中,通过设置每个子视图的frame属性来控制它们的位置和大小。
举个例子,假设我们有一个自定义的UIView,这个UIView包含了两个子视图,一个UILabel和一个UIButton。我们希望让UILabel位于UIView的顶部中央,并且它的宽度和高度都是UIView的一半。我们还希望让UIButton位于UILabel的下方,也是居中对齐,宽度和高度也都是UIView的一半。下面是一个实现这个布局的例子。
```objc
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat width = self.frame.size.width / 2.0;
CGFloat height = self.frame.size.height / 2.0;
self.label.frame = CGRectMake(0, 0, width, height);
self.label.center = CGPointMake(self.frame.size.width / 2.0, height);
self.button.frame = CGRectMake(0, height, width, height);
self.button.center = CGPointMake(self.frame.size.width / 2.0, height * 3.0);
}
```
3.2 Autoresizing布局
AutoresizingMask是一种早期的自动布局机制,它允许我们通过相对位置和大小来控制子视图。我们可以通过设置UIView的autoresizingMask属性来使用AutoresizingMask布局。
下面是一个例子。假设我们有一个UIView,它包含了一个UILabel和一个UIButton。我们希望让UILabel位于UIView的顶部中央,宽度固定为150,高度根据文本自适应。我们还希望让UIButton位于UILabel的下方,也是居中对齐,高度固定为50,宽度自适应。
```objc
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 150, 0)];
self.label.center = CGPointMake(frame.size.width / 2.0, self.label.frame.size.height / 2.0);
self.label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin;
self.button = [[UIButton alloc] initWithFrame:CGRectZero];
self.button.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
[self addSubview:self.label];
[self addSubview:self.button];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat width = self.frame.size.width / 2.0;
CGFloat height = 50.0;
self.button.frame = CGRectMake(0, self.label.frame.origin.y + self.label.frame.size.height, width, height);
self.button.center = CGPointMake(self.frame.size.width / 2.0, self.button.frame.origin.y + self.button.frame.size.height / 2.0);
}
```
在上面的例子中,UILabel的autoresizingMask被设置为UIViewAutoresizingFlexibleWidth和UIViewAutoresizingFlexibleBottomMargin。这意味着UILabel的宽度将根据其父视图的宽度自适应,并且当其父视图高度发生变化时,它会随之变化。
同样,UIButton的autoresizingMask被设置为UIViewAutoresizingFlexibleWidth和UIViewAutoresizingFlexibleTopMargin,这意味着UIButton的宽度将根据其父视图的宽度自适应,并且在其父视图的高度发生变化时,它将保持与UILabel的距离不变。
3.3 Auto Layout布局
Auto Layout是iOS 6中引入的一种新的自动布局机制。它使我们能够使用基于约束的布局来创建复杂的用户界面。相对于AutoresizingMask布局,Auto Layout布局更加灵活,更加强大。但同时,它也需要更多的学习和理解。
对于利用layoutSubviews方法进行Auto Layout布局的情况,我们需要使用NSLayoutConstraint。NSLayoutConstraint是一种表示视图之间关系的对象。我们可以使用它来描述视图之间的距离、大小和对齐关系等。
下面是一个使用Auto Layout布局的例子。假设我们有一个UIView,它包含了一个UILabel和一个UIImageView。我们希望让UILabel和UIImageView水平居中对齐,并且UILabel和UIImageView的底部和UIView的底部保持20个像素的距离。
```objc
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.label = [[UILabel alloc] init];
self.label.translatesAutoresizingMaskIntoConstraints = NO;
self.label.text = @"Hello World";
self.label.font = [UIFont systemFontOfSize:20];
self.imageView = [[UIImageView alloc] init];
self.imageView.translatesAutoresizingMaskIntoConstraints = NO;
self.imageView.image = [UIImage imageNamed:@"image"];
[self addSubview:self.label];
[self addSubview:self.imageView];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.label attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_label]-20-[_imageView]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label, _imageView)]];
}
return self;
}
```
在上面的例子中,首先我们使用了translatesAutoresizingMaskIntoConstraints = NO来取消自动布局的默认行为。接下来,我们使用NSLayoutConstraint来描述UILabel和UIImageView之间的关系。我们使用[NSLayoutConstraint constraintWithItem: attribute:relatedBy:toItem: attribute:multiplier:constant:]方法来创建NSLayoutConstraint对象,并以此描述它们之间的对齐关系。最后,我们使用NSLayoutConstraint constraintsWithVisualFormat:options:metrics:views:方法来创建底部边距的约束。
4. 总结
在这篇文章中,我们一起探讨了如何正确地利用layoutSubviews方法来布局我们的子视图。我们学习了一些基础知识,并且展示了一些使用这种方法进行手动布局、AutoresizingMask布局和Auto Layout布局的例子。当然,这些只是其中的一部分,我们还可以利用layoutSubviews方法实现其他更复杂的布局。
虽然在现代iOS应用程序中,使用Auto Layout布局已经成为标准做法,但是了解如何利用layoutSubviews方法来布局子视图仍然是很有价值的。无论是在大型的老项目中,还是在一些不能(或者不应该)使用Auto Layout的场景中,我们都应该掌握这个技能。