開發(fā)工作中,有一大部分時(shí)間都是在布局UI界面,可想而知能熟練的布局怎诫,可以很大程度的提高工作效率,如果對(duì)布局知識(shí)掌握不嫻熟贷痪,在布局過程中也會(huì)遇到一些奇葩的問題幻妓,最常見的是約束沖突報(bào)錯(cuò),不知不覺從某個(gè)系統(tǒng)版本開始后就一直出現(xiàn)約束沖突報(bào)錯(cuò)呢诬,很是無奈涌哲,下面是我總結(jié)的一些布局的小知識(shí)點(diǎn)胖缤。
一尚镰、iOS布局的幾種幾種方式:
Frame:
這種布局是性能最高的一種方式,你要看嗎哪廓?我并不打算寫
Autoresizing:
autoresizing是UIView的屬性狗唉,一直都有,使用簡(jiǎn)單涡真,但是沒有autolayout強(qiáng)大分俯,它提供了兩個(gè)屬性肾筐,如下:
@property(nonatomic) BOOL autoresizesSubviews; // default is YES. if set, subviews are adjusted according to their autoresizingMask if self.bounds changes
@property(nonatomic) UIViewAutoresizing autoresizingMask; // simple resize. default is UIViewAutoresizingNone
autoresizingMask的枚舉如下:
UIViewAutoresizingNone view的frame不會(huì)隨superview的改變而改變
UIViewAutoresizingFlexibleLeftMargin 自動(dòng)調(diào)整view與superview左邊的距離保證右邊距離不變
UIViewAutoresizingFlexibleWidth 自動(dòng)調(diào)整view的寬,保證與superView的左右邊距不變
UIViewAutoresizingFlexibleRightMargin 自動(dòng)調(diào)整view與superview右邊的距離保證左邊距不變
UIViewAutoresizingFlexibleTopMargin 自動(dòng)調(diào)整view與superview頂部的距離保證底部距離不變
UIViewAutoresizingFlexibleHeight 自動(dòng)調(diào)整view的高缸剪,保證與superView的頂部和底部距離不變
UIViewAutoresizingFlexibleBottomMargin 自動(dòng)調(diào)整view與superview底部部的距離保證頂部距離不變
當(dāng)UIView的autoresizesSubviews
是YES時(shí)吗铐,(默認(rèn)是YES), 那么在其中的子view會(huì)根據(jù)子視圖自身的autoresizingMask
屬性來自動(dòng)適應(yīng)其與superView之間的位置和大小。也就是說第一個(gè)屬性是決定子視圖能不能autoresize杏节,第二個(gè)屬性是當(dāng)自己的父視圖允許autoresize的情況下來決定自身的布局方式的唬渗,兩者缺一不可。
對(duì)于同一個(gè)父視圖的不同子視圖奋渔,他們之間的相互約束镊逝,autoresize是無法做到的,這時(shí)可以用強(qiáng)大的autoLayout了
【使用注意事項(xiàng)】在使用Xib或者SB時(shí)嫉鲸,Xcode默認(rèn)是autolayout撑蒜。需要取消勾選。在尺寸檢測(cè)器中玄渗,就可以直接設(shè)置座菠。在代碼創(chuàng)建過程中,Xcode默認(rèn)是autoresizing藤树。 直接設(shè)置就好啦辈灼。當(dāng)約束確定后可以修改frame。
AutoLayout:
iOS6之后自動(dòng)布局出來以后也榄,很受歡迎巡莹,為此蘋果還設(shè)計(jì)了VFL可視化語言,但是編碼很麻煩甜紫,于是就出現(xiàn)了鏈?zhǔn)秸Z法的三方庫(kù)masonry降宅,masonry是一個(gè)對(duì)autoLayout的技術(shù)的一個(gè)封裝庫(kù),很受大家歡迎.
【使用注意事項(xiàng)】
- 當(dāng)使用autolayout布局完成后獲取到的Frame是不對(duì)的囚霸,可以在DidLayout方法中獲取正確的Frame腰根。
- 當(dāng)使用autolayout布局translatesAutoresizingMaskIntoConstraints時(shí)其值應(yīng)該設(shè)置為NO,因?yàn)閍utoresize的布局轉(zhuǎn)換成autolayout的約束的時(shí)候拓型,可能會(huì)與已有的autolayout約束沖突额嘿,如果使用masonry布局則不需要手動(dòng)設(shè)置這個(gè)屬性。
masonry的具體用法就不說了劣挫,GitHub上有詳細(xì)的用法册养,推薦一篇博客Masonry的使用方式。
self-sizing:
在iOS 8中压固,蘋果引入了UITableView的一項(xiàng)新功能--Self Sizing Cells球拦,對(duì)于不少開發(fā)者來說這是新SDK中一項(xiàng)非常有用的新功能。在iOS 8之前,如果想在列表視圖中展示可變高度的動(dòng)態(tài)內(nèi)容時(shí)坎炼,你需要手動(dòng)計(jì)算行高愧膀,而Self Sizing Cells為展示動(dòng)態(tài)內(nèi)容提供了一個(gè)解決方案。以下是你使用Self Sizing Cells時(shí)需要注意的事項(xiàng):
- 為原型單元格定義Auto Layout約束
- 指定表視圖的estimatedRowHeight
- 將表視圖的rowHeight屬性設(shè)置為UITableViewAutomaticDimension
如果用代碼表示最后兩點(diǎn)谣光,那就是
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
僅有兩行代碼檩淋,你通知表視圖計(jì)算單元格的尺寸以匹配內(nèi)容和和動(dòng)態(tài)進(jìn)行渲染。Self Sizing Cells功能可以為你節(jié)省大量寫代碼的時(shí)間萄金。
【注意】不用Auto Layout布局Self Sizing Cells功能也是無法工作的狼钮,它依賴約束來確定合適的行高。事實(shí)上捡絮,表視圖會(huì)調(diào)用systemLayoutSizeFittingSize
并返回基于約束布局下的單元格的尺寸熬芜,具體原理看。
如果你是頭一次使用Auto Layout福稳,推薦你先看下Auto Layout Introduction涎拉。
二、幾點(diǎn)值得注意的布局技巧:
- 開發(fā)中最常用的還是masrony的圆,其提供的一個(gè)利于debug約束問題的方法鼓拧,代碼摘自masonry的demo上的,我加了注釋
UIView *greenView = UIView.new;
greenView.backgroundColor = UIColor.greenColor;
[self addSubview:greenView];
UIView *redView = UIView.new;
redView.backgroundColor = UIColor.redColor;
[self addSubview:redView];
UILabel *blueView = UILabel.new;
blueView.backgroundColor = UIColor.blueColor;
[self addSubview:blueView];
UIView *superview = self;
int padding = 10;
// 給視圖添加key的方式有兩種越妈,如下所示
// 法一:
//you can attach debug keys to views like so:
// greenView.mas_key = @"greenView";
// redView.mas_key = @"redView";
// blueView.mas_key = @"blueView";
// superview.mas_key = @"superview";
// 法二:
//OR you can attach keys automagically like so:
MASAttachKeys(greenView, redView, blueView, superview);
// 給約束添加key
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
//you can also attach debug keys to constaints
make.edges.equalTo(@1).key(@"ConflictingConstraint"); //composite constraint keys will be indexed
make.height.greaterThanOrEqualTo(@5000).key(@"ConstantConstraint");
make.top.equalTo(greenView.mas_bottom).offset(padding);
make.left.equalTo(superview.mas_left).offset(padding);
make.bottom.equalTo(superview.mas_bottom).offset(-padding).key(@"BottomConstraint");
make.right.equalTo(superview.mas_right).offset(-padding);
make.height.equalTo(greenView.mas_height);
make.height.equalTo(redView.mas_height).key(@340954); //anything can be a key
}];
return self;
}
上面的約束有問題季俩,會(huì)拋出問題,如果不給視圖和約束設(shè)置key的話梅掠,xcode提示錯(cuò)誤如下:
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<MASLayoutConstraint:0x6000000a5e20 UILabel:0x7fb318c2c990.left == MASExampleDebuggingView:0x7fb318c15c00.left + 1>",
"<MASLayoutConstraint:0x6080000a40e0 UILabel:0x7fb318c2c990.left == MASExampleDebuggingView:0x7fb318c15c00.left + 10>"
)
給視圖和約束添加了key之后的提示如下:
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<MASLayoutConstraint:ConflictingConstraint[0] UILabel:blueView.left == MASExampleDebuggingView:superview.left + 1>",
"<MASLayoutConstraint:0x6000000b7ac0 UILabel:blueView.left == MASExampleDebuggingView:superview.left + 10>"
)
是不是看著爽多了酌住。。
這樣你就能看到具體是哪個(gè)view的哪個(gè)約束可能出現(xiàn)問題了阎抒,而不是出現(xiàn)一堆的十六進(jìn)制地址
- 添加或者更新(update酪我、remake)約束的代碼應(yīng)該放在哪,代碼如下一看便知
// 當(dāng)你使用autoLayout布局的時(shí)候建議寫此方法且叁,防止autoresize布局造成的錯(cuò)誤
+ (BOOL)requiresConstraintBasedLayout
{
return YES;
}
// this is Apple's recommended place for adding/updating constraints
// 蘋果推薦添加或者更新(update都哭、remake)約束的地方
- (void)updateConstraints {
[self.growingButton updateConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
make.width.equalTo(@(self.buttonSize.width)).priorityLow();
make.height.equalTo(@(self.buttonSize.height)).priorityLow();
make.width.lessThanOrEqualTo(self);
make.height.lessThanOrEqualTo(self);
}];
// according to apple super should be called at end of method
[super updateConstraints];
}
- autoLayout不允許對(duì)其屬性例如左、右逞带、centerY等被設(shè)置為常量欺矫,因此如果你要給這些屬性傳遞一個(gè)NSNumber類型的值得時(shí)候masonry會(huì)將他們轉(zhuǎn)換成與父視圖相關(guān)的約束。
However Auto Layout does not allow alignment attributes such as left, right, centerY etc to be set to constant values.
So if you pass a NSNumber for these attributes
Masonry will turn these into constraints relative to the view’s superview ie:
[view makeConstraints:^(MASConstraintMaker *make) {
make.left.lessThanOrEqualTo(@10)
}];
view的左邊距等價(jià)于 view.left = view.superview.left + 10
- 按比例布局展氓,如果各占一半的話穆趴,也可以不用multipliedBy,直接約束兩個(gè)視圖的width isEqual就行了
// topInnerView的寬度是高度的1/3
[self.topInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(self.topInnerView.mas_height).multipliedBy(3);
]
你用NSAutoLyoutConstraints布局的時(shí)候需要設(shè)置視圖的
view1.translatesAutoresizingMaskIntoConstraints = NO
带饱,默認(rèn)值是YES毡代,等于YES的時(shí)候autoresize會(huì)影響autolayout布局阅羹,有時(shí)會(huì)發(fā)現(xiàn)效果不是自己想要的勺疼,不過如果你用masonry設(shè)置約束的時(shí)候教寂,masonry會(huì)幫你把這個(gè)屬性值設(shè)置為NO,你不用管它执庐,寫出來就是想提醒你酪耕。看完官方的demo,發(fā)現(xiàn)他們會(huì)把需要的每個(gè)約束都寫上轨淌,但是有時(shí)候不需要全寫上迂烁,如下面被我注釋的代碼,但是官方是沒有注釋的递鹉,既然人家官方都這樣寫盟步,你是不是也應(yīng)該這樣寫啊,別注釋了躏结,這樣不容易出錯(cuò)却盘,如下:
UIView *superview = self;
int padding = 10;
//if you want to use Masonry without the mas_ prefix
//define MAS_SHORTHAND before importing Masonry.h see Masonry iOS Examples-Prefix.pch
[greenView makeConstraints:^(MASConstraintMaker *make) {
make.top.greaterThanOrEqualTo(superview.top).offset(padding);
make.left.equalTo(superview.left).offset(padding);
make.bottom.equalTo(blueView.top).offset(-padding);
make.right.equalTo(redView.left).offset(-padding);
make.width.equalTo(redView).multipliedBy(1);
make.height.equalTo(redView.height);
make.height.equalTo(blueView.height);
}];
//with is semantic and option
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding); //with with
//make.left.equalTo(greenView.mas_right).offset(padding); //without with
make.bottom.equalTo(blueView.mas_top).offset(-padding);
make.right.equalTo(superview.mas_right).offset(-padding);
// make.width.equalTo(greenView).multipliedBy(1);
make.height.equalTo(@[greenView, blueView]); //can pass array of views
}];
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(greenView.mas_bottom).offset(padding);
make.left.equalTo(superview.mas_left).offset(padding);
make.bottom.equalTo(superview.mas_bottom).offset(-padding);
make.right.equalTo(superview.mas_right).offset(-padding);
make.height.equalTo(@[greenView.mas_height, redView.mas_height]); //can pass array of attributes
}];
return self;
}
要注意blueView設(shè)置高度依賴的時(shí)候設(shè)置的是一個(gè)數(shù)組這樣的用法
- masonry動(dòng)畫
@implementation MASExampleUpdateView
- (id)init {
self = [super init];
if (!self) return nil;
self.growingButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.growingButton setTitle:@"Grow Me!" forState:UIControlStateNormal];
self.growingButton.layer.borderColor = UIColor.greenColor.CGColor;
self.growingButton.layer.borderWidth = 3;
[self.growingButton addTarget:self action:@selector(didTapGrowButton:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:self.growingButton];
self.buttonSize = CGSizeMake(100, 100);
return self;
}
+ (BOOL)requiresConstraintBasedLayout
{
return YES;
}
// this is Apple's recommended place for adding/updating constraints
// 蘋果推薦添加或或者更新約束的地方
- (void)updateConstraints {
[self.growingButton updateConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
make.width.equalTo(@(self.buttonSize.width)).priorityLow();
make.height.equalTo(@(self.buttonSize.height)).priorityLow();
make.width.lessThanOrEqualTo(self);
make.height.lessThanOrEqualTo(self);
}];
// according to apple super should be called at end of method
[super updateConstraints];
}
- (void)didTapGrowButton:(UIButton *)button {
self.buttonSize = CGSizeMake(self.buttonSize.width * 1.3, self.buttonSize.height * 1.3);
// tell constraints they need updating
// 告訴約束系統(tǒng)要更新,系統(tǒng)會(huì)調(diào)用上面重寫的updateConstraints方法
[self setNeedsUpdateConstraints];
// update constraints now so we can animate the change,
// it will be call by system automatically
// 該方法不必手動(dòng)調(diào)用
// [self updateConstraintsIfNeeded];
// 可以用layoutIfNeeded來實(shí)現(xiàn)即時(shí)更新媳拴,還可以添加動(dòng)畫
[UIView animateWithDuration:0.4 animations:^{
[self layoutIfNeeded]; // 需要在此處調(diào)用layoutIfNeeded方法才能產(chǎn)生動(dòng)畫
}];
}
@end
- masonry 布局scrollVIew(垂直滾動(dòng)的情況下)黄橘;
偽代碼如下
[self.view addSubView:self.scrollView];
[scrollView addSubView:self.subViews];
[self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
make.bottom.equalTo(self.subViews); // 此處要注意
}];
[self.subViews mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.scrollView); //此處注意
make.left.right.equalTo(self.view); // 此處注意
make.height.equalTo(@(1000));
}];
對(duì)于垂直滑動(dòng)的scrollView
來說,top
屈溉、left
塞关、right
的邊距要等于self.view
的邊距,但是bottom要依賴于子視圖子巾。
對(duì)于處于scrollView
的子視圖來說帆赢,left
、right
的邊距要等于self.view
线梗,top
匿醒、bottom
的邊距要依賴于scrollView
- 布局水平方向兩個(gè)寬度不固定的視圖,詳情