AutoLayout之关于苹果原生约束的探索
AutoLayout(自动布局),在我们的项目中,我更喜欢把它称为约束。iOS实现约束有几种方式:原生约束api、VFL、IB、第三方约束工具(Masonry、UIView+AutoLayout),这里花一点篇幅来讲述苹果原生的约束。
github:https://github.com/yangqingren/LBAutoLayout
NSLayoutConstraint
我们先来阅读以下官方注释:
/* Create constraints explicitly.
Constraints are of the form
"view1.attr1 = view2.attr2 * multiplier + constant"
If your equation does not have a second view and attribute, use nil and NSLayoutAttributeNotAnAttribute.
*/
+(instancetype)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(nullable id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)c;
大概的意思就是view1的约束等于(等于or不超过or不低于)view2的约束的n倍+常量。
- (UILabel *)lbLabel {
if (!_lbLabel) {
_lbLabel = [[UILabel alloc] init];
_lbLabel.text = @"我是用来测试约束的Label";
_lbLabel.backgroundColor = [UIColor yellowColor];
_lbLabel.translatesAutoresizingMaskIntoConstraints = NO;
}
return _lbLabel;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.lbLabel];
// 设置label的top等于self.view的top,倍数为1,offset为偏移80
NSLayoutConstraint *topConstraint =
[NSLayoutConstraint constraintWithItem:self.lbLabel
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1
constant:80];
// 设置label的left等于self.view的left,倍数为1,offset为偏移80
NSLayoutConstraint *leftConstraint =
[NSLayoutConstraint constraintWithItem:self.lbLabel
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1
constant:80];
[self.view addConstraints:@[topConstraint, leftConstraint]];
// Do any additional setup after loading the view, typically from a nib.
}
可以看到,只要设置label的top约束和left约束,即可完成布局,也就是说,label(button)约束布局时自带宽高,宽高由其text填充,关于这个,我后面再讲。
由于label的这种特性,我们可以使用简单的约束就可以完成以下的这几种布局:
// shadowLabel的right等于lbLabel的left,达到宽度自适应
NSLayoutConstraint *shadowConstraint =
[NSLayoutConstraint constraintWithItem:self.shadowLabel
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.lbLabel
attribute:NSLayoutAttributeRight
multiplier:1
constant:0];
或者
// shadowLabel的top等于lbLabel的bottom,达到高度自适应
NSLayoutConstraint *shadowConstraint =
[NSLayoutConstraint constraintWithItem:self.shadowLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.lbLabel
attribute:NSLayoutAttributeBottom
multiplier:1
constant:0];
当然也可以设置固定长度
NSLayoutConstraint *widthConstraint =
[NSLayoutConstraint constraintWithItem:self.lbLabel
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:150];
补充:关于AutoLayout与Frame的思考
由于给view设置约束的时候,并没有马上生成相应的frame,这会使得如果对使用约束布局的view获取相应的bounds等操作的时候,获取到的只有CGSizeZero,这里提供一些思路与方案:
// 1.使用layoutIfNeeded,让刚刚设置约束的view立即去布局,之后就有对应的size了
[view layoutIfNeeded];
// 2.在layoutSubviews里进行获取
- (void)layoutSubviews {
[super layoutSubviews];
NSLog(@"self.bounds=%@",self.bounds);
}
// 但是layoutSubviews会多次调用,这里需要十分注意
上面说到label自带宽高,这里拓展讲解一下
UIView都有一个属性,叫做intrinsicContentSize
当label(button) setText的时候,系统根据字体字体大小与长度设置一个满足label的size,此时会触发一个叫intrinsicContentSize的方法,用这个方法,可以对一些不定字符长度的控件进行设置,如以下这个按钮
- (UIButton *)button {
if (!_button) {
_button = [UIButton buttonWithType:UIButtonTypeCustom];
[_button setTitle:@"Log in" forState:UIControlStateNormal];
_button.layer.cornerRadius = 4;
_button.backgroundColor = [UIColor grayColor];
}
return _button;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.button];
[self.button mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.view);
}];
// Do any additional setup after loading the view, typically from a nib.
}
你会看到它的size刚好紧贴着text,效果很差,我们可以通过重写intrinsicContentSize方法,满足布局要求
#import "LBSizeButton.h"
@interface LBSizeButton ()
@end
@implementation LBSizeButton
-(CGSize)intrinsicContentSize
{
CGSize size = [super intrinsicContentSize];
return CGSizeMake(size.width + 10 * 2, size.height + 2 * 2);
}
@end
使用aotuLayout,还可使得控件的size根据内容的进行填充,比如我们常见的表单结构:
// 禁止拉伸优先级
[self.numLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
// 禁止压缩优先级
[self.numLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
禁止拉伸、压缩优先级,可以达到以上效果
github:https://github.com/yangqingren/LBAutoLayout
下次有空写一些关于用Masonry进行布局探索的实例
end