题图
Preface(废话)
在iOS
开发中一直有一个很具争议的话题,那就是界面布局到底是代码好还是使用IB(xib/storyboard)
好?
有些人觉得手撸代码,才叫coder
,使用代码的编译速度快(快不快苹果说了算)
有些人觉得IB
的开发效率不容置疑,而且能减少界面代码对工程的污染
还有一些人觉得计算frame
挺有意思…
好吧,如果还在使用initWithFrame()
计算界面的布局,请在本文的留言区默默的扣个1...
本文不打算讨论哪个好,毕竟这么些年了,没有得出啥好的结论,代码的还是啪啪;IB
也玩的很开心;
咱们把所有的方式都罗列一下,各位看官各取所需
手撸代码
Frame党
在autolayout
没有出来的时候,你使用frame
也就不说啥了,因为在那个时候手机屏幕比较单一,就只有320*480一个,frame
随便计算,但是现在这个时代那么多屏幕,你还是这个...
如果只是简单界面,你frame
一下也没啥,请看下面的示例代码:
// 分割线
UILabel *label1 = [[UILabel alloc] initWithFrame:CGRectMake(8, 58, Width - 16, 1)];
label1.backgroundColor = SEPARATORLINE;
[self addSubview:label1];
UILabel *label2 = [[UILabel alloc] initWithFrame:CGRectMake(8, 116, Width - 16, 1)];
label2.backgroundColor = SEPARATORLINE;
[self addSubview:label2];
// 任务目标
UILabel *aim = [[UILabel alloc] initWithFrame:CGRectMake(8, 8, 70, 21)];
[self setLabel:aim WithTitle:@"任务目标:" withFont:FONT(15) withColor:PD_NAVI_COLOR];
_taskAimLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, 29, Width - 16, 21)];
_taskAimLabel.font = FONT(13);
[self addSubview:_taskAimLabel];
// 任务奖励
UILabel *award = [[UILabel alloc] initWithFrame:CGRectMake(8, 67, Width - 16, 21)];
[self setLabel:award WithTitle:@"任务奖励:" withFont:FONT(15) withColor:PD_NAVI_COLOR];
_taskAwardLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, 88, Width - 16, 21)];
_taskAwardLabel.font = FONT(13);
[self addSubview:_taskAwardLabel];
// 任务描述
UILabel *des = [[UILabel alloc] initWithFrame:CGRectMake(8, 124, Width - 16, 21)];
[self setLabel:des WithTitle:@"任务描述:" withFont:FONT(15) withColor:PD_NAVI_COLOR];
_taskDescriptionLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, 145, Width - 16, 42)];
_taskDescriptionLabel.font = FONT(13);
_taskDescriptionLabel.numberOfLines = 2;
[self addSubview:_taskDescriptionLabel];
... ...
我只想问,同学你真的不累么?你不累我这个维护者看着都累,如果是一个很长且布局不重复的界面,你咋办?
估计自己算着算着就晕了
所以,对于frame
党,我只想说,你需要一个计算器...
手写Constraints
其实手写Constraints
也是相当麻烦的,主要是因为Constraints
有着非人的语法,给个例子大家随意感受一下
constraint = [
NSLayoutConstraint
constraintWithItem:testButton
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0f
constant:00.0f
];
[self.view addConstraint:constraint];
constraint = [
NSLayoutConstraint
constraintWithItem:testButton
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:-20.0f
];
[self.view addConstraint:constraint];
上面这段代码,只是添加了两个约束,试想一下,如果我们要写一个比较复杂的界面时,得写多长的代码?
不过,上面的代码可以优化
NSDictionary *viewsDic = NSDictionaryOfVariableBindings(deleteButton,cancelButton,nextButton);
NSArray *constraints = nil;
constraints = [NSLayoutConstraint constraintsWithVisualFormat:
@"H:|-25-[deleteButton(==cancelButton@700)]-(>=8)-[cancelButton(140)]-[nextButton(nextButtonWidth)]-rectY-|"
options:NSLayoutFormatAlignAllTop
metrics:@{@"rectY":@5,@"nextButtonWidth":@30}
views:viewsDic];
[self.view addConstraints:constraints];
上面的代码看懂了么?
我自己都看不懂...
但是总有一些程序员和别的程序员不一样,他们能有不一样的能力,他们叫做大神,大神给我们封装了Masonry/Snapkit
Masonry/Snapkit
Masonry是一个轻量级的布局框架,拥有自己的描述语法,采用更优雅的链式语法封装自动布局,简洁明了,并具有高可读性,而且同时支持iOS
和 Max OS X
;Snapkit是Masonry
的swift
版本,语法也差不多
先看一个例子再说:
// masonry
[testButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self);
make.size.mas_equalTo(CGSizeMake(50, 50));
make.bottom.equalTo(hanupLabel.mas_top).offset(-10);
}];
// snapkit
testButton.snp_makeConstraints(closure: { make in
make.centerX.equalTo(self);
make.size.mas_equalTo(CGSizeMake(50, 50));
make.bottom.equalTo(hanupLabel.mas_top).offset(-10);
})
控件直接调用mas_makeConstraints
方法,在block
中使用MASConstraintMaker
进行相应的布局就行了,非常简洁
最重要的是可读性很高,在masonry
里面有几点需要注意的地方:
- 必须先把控件添加到父视图以后才能进行约束,不然会
crash
mas_equalTo
,在约束值为具体的数值(CGSize
,CGPoint
等也是)的时候需要使用这个- 内存管理,约束的
block
里面有时候会引起隐式内存泄露 - 重复约束,或约束出现冲突的时候,在控制台会有
log
输出,发现以后改掉就好了 - 如果多个约束在一行时,使用连接符语法(
and
),增强代码的可读性
上面几条是笔者在使用过程中总结的一些经验,大家可以参考一下,其实在这里内存管理方面,很值得说到;不过笔者在之前已经写过相关OC内存管理和Swift内存管理的文章了大家可以出门左转进去看看,就能找到解决隐式内存泄露问题的办法