layoutsubviews、setneedslayout 和 layoutifneeded

layoutIfNeeded 可能的实现机制

1
2
3
4
5
6
7
8
9
10
-(void)layoutIfNeeded {
if (self._needsLayout) {
UIView *sv = self.superview;
if (sv._needsLayout) {
[sv layoutIfNeeded];
} else {
[self layoutSubviews];
}
}
}

其中 _needsLayout 由 setNeedsLayout 进行设置。

使用示例

现在自定义一个 UICustomView 继承自 UIView, 这个 UICustomView 中包含一个 contentView 属性和一个 edgeInsets 属性来控制contentView 的 frame。
这个自定义的 UICustomView 如下:

1
2
3
4
5
6
#import <UIKit/UIKit.h>

@interface UICustomView : UIView
@property (nonatomic, strong) UIView * contentView;
@property (nonatomic, assign) UIEdgeInsets edgeInsets;
@end
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
#import "UICustomView.h"

@implementation UICustomView

- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
_contentView = [[UIView alloc] initWithFrame:self.bounds];
[self addSubview:_contentView];
}
return self;
}

- (void)layoutSubviews{
[super layoutSubviews];
self.contentView.frame = CGRectMake(
self.bounds.origin.x + self.edgeInsets.left,
self.bounds.origin.y + self.edgeInsets.top,
self.bounds.size.width - self.edgeInsets.left - self.edgeInsets.right,
self.bounds.size.height - self.edgeInsets.top - self.edgeInsets.bottom);

NSLog(@"调用===layoutSubviews %@",self);
}

- (void)setEdgeInsets:(UIEdgeInsets)edgeInsets{
_edgeInsets = edgeInsets;
[self setNeedsLayout];
[self layoutIfNeeded];
}

上面的 UICustomView 重写了 layoutSubViews 方法,并且实现了当 edgeInsets 的值发生改变,contentView 的 frame 随及发生改变。

当执行下面的动画:

1
2
3
[UIView animateWithDuration:2 animations:^{
self.edgeInsets = UIEdgeInsetsMake(45, 17, 18, 34);
}];

contentView 动态变小,如果 setEdgeInsets: 方法中不调用 layoutIfNeeded,那么 contentView 的变小不会在 animate 的 block 中,而是在 block 外,也就动态变小。如果 setEdgeInsets: 方法中 只调用 layoutIfNeeded,那么 contentView 的大小不会改变,因为此时 setNeedsLayout 并没有标记。

参考:What is the relationship between UIView’s setNeedsLayout, layoutIfNeeded and layoutSubviews?