現(xiàn)在iOS頁面布局用的最多的就是Frame和Autolayout,在Autolayout通過Masonry封裝在實際使用中也十分方便叫惊。實際上,Autolayout的約束最后都是系統(tǒng)最終轉(zhuǎn)化成frame來進行布局的,對與一個View來說轴踱,最終確定其中心點位置和View的寬高埃元。當Autolayout和Frame設(shè)置上產(chǎn)生沖突的時候涝涤,則會以Autolayout的設(shè)置為準。這篇主要討論布局中常用的幾個方法和autolayout遇到動畫的情形岛杀。
跟布局相關(guān)的方法
- (void)setNeedsLayout;
- (void)layoutIfNeeded;
- (void)layoutSubviews;
setNeedsLayout方法標記當前view是需要重新布局的阔拳,在下一次runloop中,進行重新布局类嗤。如果說想在當前runloop中立刻更新布局糊肠,則通過調(diào)用layoutIfNeeded方法可以實現(xiàn),此時系統(tǒng)會調(diào)用layoutSubviews遗锣。在layoutSubviews方法中货裹,可以自己定義新的view或者改變子view的布局。
Autolayout相關(guān)的方法
//view的方法
- (void)updateConstraintsIfNeeded;
//重寫view中的方法
- (void)updateConstraints
- (BOOL)needsUpdateConstraints
- (void)setNeedsUpdateConstraints
//重寫viewController中的方法
- (void)updateViewConstraints
setNeedsUpdateConstraints只是標記當前view的約束需要在下一次runloop中更新精偿,updateConstraintsIfNeeded如果過滿足更新條件會立刻調(diào)用updateConstraints來更新約束弧圆,updateConstraints是子view需要重寫的方法,來更新View的約束笔咽,最后需要調(diào)用[super updateConstraints]搔预,否則會崩。而updateViewConstraints是定義在viewController中的拓轻,方便去更新viewController對應(yīng)view的約束斯撮。
具體可以通過調(diào)用view的setNeedsUpdateConstraints來最終調(diào)用到viewController的updateViewConstraints方法來,如果沒有這個方法扶叉,那么每次都要定義一個子view去重寫updateConstraints方法會比較繁瑣勿锅。updateConstraints和updateViewConstrains方法可以把約束的代碼和和業(yè)務(wù)邏輯分開帕膜,另外性能也更好。
為什么會有setxxxxx和xxxifNeeded方法
setxxxxx方法可能是為了性能溢十,沒有在代碼更新布局或者約束之后立刻執(zhí)行垮刹,而是在下一次runloop中執(zhí)行。
xxxxIfNeeded方法則是為了在必要的時候张弛,立刻更新約束或者布局荒典,舉個例子,有些時候同時使用動畫和autolayout吞鸭。
Autolayout和動畫
現(xiàn)在實現(xiàn)一個view滑動的動畫:
@interface MyView : UIView
-(void)layoutSubviews;
@end
@implementation MyView
-(void)layoutSubviews
{
[super layoutSubviews];
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
_container = [[MyView alloc] initWithFrame:self.view.frame];
[self.view addSubview:_container];
_redView = [[MyView alloc] initWithFrame:CGRectMake(100, 100, 100, 50)];
_redView.backgroundColor = [UIColor redColor];
[_container addSubview:_redView];
[_redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.centerY.equalTo(self.view);
make.width.height.equalTo(@100);
}];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.titleLabel.text = @"點擊動畫";
btn.backgroundColor = [UIColor greenColor];
[_container addSubview:btn];
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.centerY.equalTo(self.view).offset(100);
make.width.equalTo(@100);
make.height.equalTo(@50);
}];
[btn addTarget:self action:@selector(onButtonClick:) forControlEvents:
UIControlEventTouchUpInside];
}
使用frame
- (void)onButtonClick:(id)sender
{
[UIView animateWithDuration:1 animations:^{
self.redView.frame = CGRectMake(0, self.redView.frame.origin.y, self.
redView.frame.size.width, self.redView.frame.size.height);
}];
}
和預(yù)期結(jié)果一致寺董,紅色的view滑動到屏幕的最左側(cè)。
使用autolayout
- (void)onButtonClick:(id)sender
{
[UIView animateWithDuration:1 animations:^{
[self.redView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.width.height.equalTo(@100);
make.left.centerY.equalTo(self.view);
}];
}
和預(yù)期的結(jié)果不一致刻剥,紅色的view突然一下移動到屏幕的最左側(cè)遮咖,上面這種做法是有問題的,現(xiàn)在在動畫的block中添加一行日志的代碼造虏。
- (void)onButtonClick:(id)sender
{
[UIView animateWithDuration:1 animations:^{
[self.redView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.width.height.equalTo(@100);
make.left.centerY.equalTo(self.view);
}];
NSLog(@"redView x = %@", @(self.redView.frame.origin.x));
}];
}
輸出日志:
2016-09-07 17:44:03.069 2323[99216:10267632] redView x = 137.5(原來的位置)
說明在重新更新紅色View的約束之后御吞,系統(tǒng)并沒有立刻轉(zhuǎn)化成對應(yīng)的frame值,還是原來的位置漓藕,但是為什么view會移動到屏幕最右側(cè)而不是靜止呢陶珠。
因為系統(tǒng)在計算動畫插值的過程中,發(fā)現(xiàn)紅色view的前后的位置是一樣的享钞,最后的結(jié)果就是在原地靜止揍诽,猜測有可能系統(tǒng)為了優(yōu)化直接取消了動畫。這是在當前runloop發(fā)生的邏輯嫩与,在下一次runloop過程中寝姿,上一次給紅色view設(shè)置的約束就生效了,系統(tǒng)會將約束更新到frame的表現(xiàn)上划滋,所以紅色view直接跑到了屏幕最左側(cè)饵筑。
現(xiàn)在的問題是需要在系統(tǒng)計算動畫插值的時候,將視圖的frame即時的更新处坪,所以調(diào)用紅色view的layoutIfNeeded方法就好了根资。
- (void)onButtonClick:(id)sender
{
[UIView animateWithDuration:1 animations:^{
[self.redView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.width.height.equalTo(@100);
make.left.centerY.equalTo(self.view);
}];
NSLog(@"redView x = %@", @(self.redView.frame.origin.x));
[self.container layoutIfNeeded];
NSLog(@"redView x = %@", @(self.redView.frame.origin.x));
}];
}
日志輸出為:
2016-09-07 17:50:33.297 2323[99250:10270639] redView x = 137.5
2016-09-07 17:50:33.299 2323[99250:10270639] redView x = 0
通過對比可以看到,在調(diào)用parentView的layoutIfNeeded之后同窘,其frame得到更新了玄帕,所以最后動畫如預(yù)期一樣出現(xiàn)了。這里必須是調(diào)用parentView的layoutIfNeeded方法想邦,調(diào)用紅色視圖的layoutIfNeeded方法是不會更新它自己的frame的裤纹。