iOS 布局总结

开发工作中,有一大部分时间都是在布局UI界面,可想而知能熟练的布局,可以很大程度的提高工作效率,如果对布局知识掌握不娴熟,在布局过程中也会遇到一些奇葩的问题,最常见的是约束冲突报错,不知不觉从某个系统版本开始后就一直出现约束冲突报错,很是无奈,下面是我总结的一些布局的小知识点。

一、iOS布局的几种几种方式:

Frame:

这种布局是性能最高的一种方式,你要看吗?我并不打算写

Autoresizing:

autoresizing是UIView的属性,一直都有,使用简单,但是没有autolayout强大,它提供了两个属性,如下:

@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不会随superview的改变而改变
UIViewAutoresizingFlexibleLeftMargin 自动调整view与superview左边的距离保证右边距离不变
UIViewAutoresizingFlexibleWidth 自动调整view的宽,保证与superView的左右边距不变
UIViewAutoresizingFlexibleRightMargin 自动调整view与superview右边的距离保证左边距不变
UIViewAutoresizingFlexibleTopMargin 自动调整view与superview顶部的距离保证底部距离不变
UIViewAutoresizingFlexibleHeight 自动调整view的高,保证与superView的顶部和底部距离不变
UIViewAutoresizingFlexibleBottomMargin 自动调整view与superview底部部的距离保证顶部距离不变

当UIView的autoresizesSubviews是YES时,(默认是YES), 那么在其中的子view会根据子视图自身的autoresizingMask属性来自动适应其与superView之间的位置和大小。也就是说第一个属性是决定子视图能不能autoresize,第二个属性是当自己的父视图允许autoresize的情况下来决定自身的布局方式的,两者缺一不可。

对于同一个父视图的不同子视图,他们之间的相互约束,autoresize是无法做到的,这时可以用强大的autoLayout了

【使用注意事项】在使用Xib或者SB时,Xcode默认是autolayout。需要取消勾选。在尺寸检测器中,就可以直接设置。在代码创建过程中,Xcode默认是autoresizing。 直接设置就好啦。当约束确定后可以修改frame。

AutoLayout:

iOS6之后自动布局出来以后,很受欢迎,为此苹果还设计了VFL可视化语言,但是编码很麻烦,于是就出现了链式语法的三方库masonry,masonry是一个对autoLayout的技术的一个封装库,很受大家欢迎.

【使用注意事项】

  1. 当使用autolayout布局完成后获取到的Frame是不对的,可以在DidLayout方法中获取正确的Frame。
  2. 当使用autolayout布局translatesAutoresizingMaskIntoConstraints时其值应该设置为NO,因为autoresize的布局转换成autolayout的约束的时候,可能会与已有的autolayout约束冲突,如果使用masonry布局则不需要手动设置这个属性。

masonry的具体用法就不说了,GitHub上有详细的用法,推荐一篇博客Masonry的使用方式

self-sizing:

在iOS 8中,苹果引入了UITableView的一项新功能--Self Sizing Cells,对于不少开发者来说这是新SDK中一项非常有用的新功能。在iOS 8之前,如果想在列表视图中展示可变高度的动态内容时,你需要手动计算行高,而Self Sizing Cells为展示动态内容提供了一个解决方案。以下是你使用Self Sizing Cells时需要注意的事项:

  1. 为原型单元格定义Auto Layout约束
  2. 指定表视图的estimatedRowHeight
  3. 将表视图的rowHeight属性设置为UITableViewAutomaticDimension

如果用代码表示最后两点,那就是

tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension

仅有两行代码,你通知表视图计算单元格的尺寸以匹配内容和和动态进行渲染。Self Sizing Cells功能可以为你节省大量写代码的时间。

【注意】不用Auto Layout布局Self Sizing Cells功能也是无法工作的,它依赖约束来确定合适的行高。事实上,表视图会调用systemLayoutSizeFittingSize并返回基于约束布局下的单元格的尺寸,具体原理看

如果你是头一次使用Auto Layout,推荐你先看下Auto Layout Introduction

二、几点值得注意的布局技巧:

  1. 开发中最常用的还是masrony,其提供的一个利于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;
}

上面的约束有问题,会抛出问题,如果不给视图和约束设置key的话,xcode提示错误如下:

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>"
)

是不是看着爽多了。。
这样你就能看到具体是哪个view的哪个约束可能出现问题了,而不是出现一堆的十六进制地址

  1. 添加或者更新(update、remake)约束的代码应该放在哪,代码如下一看便知
// 当你使用autoLayout布局的时候建议写此方法,防止autoresize布局造成的错误
+ (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];
}
  1. autoLayout不允许对其属性例如左、右、centerY等被设置为常量,因此如果你要给这些属性传递一个NSNumber类型的值得时候masonry会将他们转换成与父视图相关的约束。

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的左边距等价于 view.left = view.superview.left + 10

  1. 按比例布局,如果各占一半的话,也可以不用multipliedBy,直接约束两个视图的width isEqual就行了
// topInnerView的宽度是高度的1/3
[self.topInnerView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.width.equalTo(self.topInnerView.mas_height).multipliedBy(3);
]
  1. 你用NSAutoLyoutConstraints布局的时候需要设置视图的view1.translatesAutoresizingMaskIntoConstraints = NO,默认值是YES,等于YES的时候autoresize会影响autolayout布局,有时会发现效果不是自己想要的,不过如果你用masonry设置约束的时候,masonry会帮你把这个属性值设置为NO,你不用管它,写出来就是想提醒你。

  2. 看完官方的demo,发现他们会把需要的每个约束都写上,但是有时候不需要全写上,如下面被我注释的代码,但是官方是没有注释的,既然人家官方都这样写,你是不是也应该这样写啊,别注释了,这样不容易出错,如下:

 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设置高度依赖的时候设置的是一个数组这样的用法

  1. masonry动画

@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
    // 告诉约束系统要更新,系统会调用上面重写的updateConstraints方法
    [self setNeedsUpdateConstraints];

    // update constraints now so we can animate the change,
    // it will be call by system automatically
    // 该方法不必手动调用
    // [self updateConstraintsIfNeeded];

    // 可以用layoutIfNeeded来实现即时更新,还可以添加动画
    [UIView animateWithDuration:0.4 animations:^{
        [self layoutIfNeeded]; // 需要在此处调用layoutIfNeeded方法才能产生动画
    }];
}
@end
  1. masonry 布局scrollVIew(垂直滚动的情况下);

伪代码如下

[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));
}];

对于垂直滑动的scrollView来说,topleftright的边距要等于self.view的边距,但是bottom要依赖于子视图。
对于处于scrollView的子视图来说,leftright的边距要等于self.viewtopbottom的边距要依赖于scrollView

  1. 布局水平方向两个宽度不固定的视图,详情
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,233评论 6 495
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,357评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,831评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,313评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,417评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,470评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,482评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,265评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,708评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,997评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,176评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,503评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,150评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,391评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,034评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,063评论 2 352

推荐阅读更多精彩内容