欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

AutoLayout之关于苹果原生约束的探索

程序员文章站 2022-06-08 16:31:31
...

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

AutoLayout之关于苹果原生约束的探索

当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

AutoLayout之关于苹果原生约束的探索

AutoLayout之关于苹果原生约束的探索

使用aotuLayout,还可使得控件的size根据内容的进行填充,比如我们常见的表单结构:

AutoLayout之关于苹果原生约束的探索

AutoLayout之关于苹果原生约束的探索

    // 禁止拉伸优先级
    [self.numLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
    // 禁止压缩优先级
    [self.numLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];

禁止拉伸、压缩优先级,可以达到以上效果

github:https://github.com/yangqingren/LBAutoLayout

下次有空写一些关于用Masonry进行布局探索的实例

end