AutoLayout-使用代码进行适配

最近看MBProgressHUD源文件,发现适配都是用的代码约束,之前我一直用的StoreBoard适配,借此机会顺便学习了使用AutoLayout进行代码适配,在这里记录整理下来,方便自己和有需要的朋友提供一个参考,以下内容难免有错误之处,还请看到的朋友帮我及时更正。

用到的方法

代码适配用到的方法主要就是两个:

/**
 *  正常方式约束,需传入以下几个参数
 *
 *  @param view1      要进行约束的控件
 *  @param attr1      约束属性(枚举类型),要约束控件什么属性,比如上\下\左\右\中心点..
 *  @param relation   约束关系(枚举类型),即参照控件之间的关系,大于\小于\等于..
 *  @param view2      参照控件,即view1的约束要参照的view
 *  @param attr2      要参照view2哪个属性,枚举类型
 *  @param multiplier 乘数
 *  @param c          常量
 *
 *  @return 返回一个NSLayoutConstraint对象
 */
+(instancetype)constraintWithItem:(id)view1
                    attribute:(NSLayoutAttribute)attr1
                    relatedBy:(NSLayoutRelation)relation
                       toItem:(nullable id)view2
                    attribute:(NSLayoutAttribute)attr2
                   multiplier:(CGFloat)multiplier
                     constant:(CGFloat)c;

/**
 *  VFL字符串方式约束
 *
 *  @param format  约束的VFL格式字符串
 *  @param opts    约束属性,枚举类型
 *  @param metrics 参数`format`里边的属性所对应的字典
 *  @param views   参数`format`里边的控件对应关系说明
 *
 *  @return 返回一个NSLayoutConstraint数组
 */
+ (NSArray<__kindof NSLayoutConstraint *> *)constraintsWithVisualFormat:(NSString *)format
                                                            options:(NSLayoutFormatOptions)opts
                                                            metrics:(nullable NSDictionary<NSString *,id> *)metrics
                                                              views:(NSDictionary<NSString *, id> *)views;

这样看都太抽象,看下具体到视图控件上怎么设置约束。

苹果在计算计算某个视图的约束的时候,有个万能公式,这个公式是这样的:
视图1 某个属性 = (视图2 某个属性)*乘数 + 常量,上边方法一就是根据这个公式来的,或者说,方法一可以这样理解。

设置约束:使用方法一

创建两个UIView对象
// 蓝色视图
UIView *blueView = [[UIView alloc] init];
blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:blueView];

// 红色视图
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];

// 设置blueView约束
NSMutableArray *blueViewConstrains = [NSMutableArray array];
// 设置blueView上边距,这句话意思是:blueView 上边距 = (self.view 上边距) * 1 + 50
[blueViewConstrains addObject:[NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.f constant:50.f]];
// 设置blueView左边距,这句话意思是:blueView 左边距 = (self.view 左边距) * 1 + 20
[blueViewConstrains addObject:[NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.f constant:20.f]];
// 设置blueView上边距,这句话意思是:blueView 右边距 = (self.view 右边距) * 1 + 20
[blueViewConstrains addObject:[NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.f constant:-20.f]];
// 设置blueView高度,这句话意思是:blueView 高度 = 30
[blueViewConstrains addObject:[NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:30.f]];
[self.view addConstraints:blueViewConstrains];

// 设置redView约束
NSMutableArray *redViewConstrains = [NSMutableArray array];

[redViewConstrains addObject:[NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:blueView attribute:NSLayoutAttributeBottom multiplier:1.f constant:20.f]];
[redViewConstrains addObject:[NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.f constant:20.f]];
[redViewConstrains addObject:[NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.f constant:-20.f]];
[redViewConstrains addObject:[NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:30.f]];
[self.view addConstraints:redViewConstrains];

运行代码:

未设置translatesAutoresizingMaskIntoConstraints属性时打印信息

发现这时候模拟器上并没有显示任何内容,并且控制台打印了一大推东西,这是因为我们没有给blueViewredView关闭translatesAutoresizingMaskIntoConstraints属性,在上边代码中加上两句话:

blueView.translatesAutoresizingMaskIntoConstraints = NO;
redView.translatesAutoresizingMaskIntoConstraints = NO;

运行程序:

约束方式一 竖屏
约束方式一 横屏

可以看出,现在不管是横屏还是竖屏,我们都达到了想要的效果。

设置约束:使用方法二

我们还是分别创建一个蓝色视图,一个红色视图

UIView *blueView = [[UIView alloc] init];
blueView.backgroundColor = [UIColor blueColor];
blueView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:blueView];

UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
redView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:redView];

// 设置蓝色视图约束关系
NSMutableArray *blueViewConstrains = [NSMutableArray array];
[blueViewConstrains addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[blueView]-20-|" options:0 metrics:nil views:@{@"blueView" : blueView}]];
[blueViewConstrains addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-30-[blueView(30)]" options:0 metrics:nil views:@{@"blueView" : blueView}]];
[self.view addConstraints:blueViewConstrains];

// 设置红色视图约束关系
NSMutableArray *redViewConstrains = [NSMutableArray array];
[redViewConstrains addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[redView]-20-|" options:0 metrics:nil views:@{@"redView" : redView}]];
[redViewConstrains addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[blueView]-20-[redView(==blueView)]" options:0 metrics:nil views:@{@"blueView":blueView, @"redView":redView}]];
[self.view addConstraints:redViewConstrains];

先不看为什么上边代码里边一大堆字符串是什么意思,我们先来运行下程序,发现和使用方法一设置约束的效果是相同的,说明我们设置的约束没问题。

下边让我们细细说下上边几句约束代码每句话什么意思:

/**
 *  参数一字符串是VFL语言约束字符串,这个字符串我们可以理解为视图控件的一种摆放方式
 *
 *  H: 表示水平方向
 *  - 表示间隔距离
 *  左边|表示屏幕最左边
 *  blueView是蓝色控件名字,用[]括起来表示这是个视图。在实际中我们最好保持这个名字和控件名字相同
 *  右边|表示屏幕最右边
 *
 *  把这几个参数表达的内容连接起来就是:blueView距离屏幕最左边20 距离最右边20 中间是blueView
 *
 *  options参数表示对其方式,顾名思义就是这个控件要靠哪边对其,枚举类型
 *  metrics参数表示VFL语言约束字符串里边用到的属性的属性名和属性值
 *  views参数表示VFL语言约束字符串里边控件名称对应关系,比如blueView就对应我们创建的blueView这个控件
 */
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[blueView]-20-|" options:0 metrics:nil views:@{@"blueView" : blueView}]

/** 垂直方向上的约束:blueView距离屏幕上边距30,并且blueView高度为30 */
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-30-[blueView(30)]" options:0 metrics:nil views:@{@"blueView" : blueView}]


/** 水平方线上:redView距离距离屏幕左边20,右边20。这样就保证设置了redView的x和宽度  */
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[redView]-20-|" options:0 metrics:nil views:@{@"redView" : redView}]

/** 垂直方向上:redView距离blueView20,并且redView高度=blueView高度。这样就保证设置了redView的y和高度 */
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[blueView]-20-[redView(==blueView)]" options:0 metrics:nil views:@{@"blueView":blueView, @"redView":redView}]

这里有个坑要注意,如果我们要设置redView宽度为blueView宽度一半,那么上边代码该怎么写呢?有人可能会说了,在redView@"V:[blueView]-20-[redView(==blueView)]"把这句代码改成@"V:[blueView]-20-[redView(==blueView*0.5)]"不就行了么,但是实际可以么?修改之后运行发现程序崩溃,也就是说系统不识别这种设置约束的方法

设置[redView(==blueView*0.5)]后程序崩溃

查看苹果API,发现有这样一段话:

关于这个报错API的解释

看到了吧,如果我们要设置这样的属性,必须要使用方法一,这个问题一定要注意。

tips

对于上边两个方法中最后一个参数,推荐使用NSDictionaryOfVariableBindings(..)这个宏,这个宏可以生成一个变量名到变量值映射的Dictionary。比如NSDictionaryOfVariableBindings(view1, view2)将会生成一个{@"view1" = view1, @"view2 = view2}的Dictionary。

几个值得注意的地方

  • 使用AutoLayout进行适配,请不要设置要适配的控件视图的Frame属性,即使设置了这个属性也不会起作用;
  • 务必对控件的translatesAutoresizingMaskIntoConstraints属性设置为NO,否则可能导致诸多意想不到的情况;
  • 给某个属性添加约束,一定要先把这个控件添加到视图上,否则程序会崩溃,记住是一定!一定!一定!
  • 方法一和方法二即可互为补充,又可以互为替代,实际开发中可以结合使用;
  • 如果要写框架,并且涉及到适配的时候,为了代码可维护性和效率,尽量使用代码进行适配而不是StoreBoard;
  • 我们再给控件设置frame的时候,要保证x、y、宽度和高度都要设置,设置约束也是,必须要保证x、y、宽度和高度上都要设置;

约束应该添加到哪个视图呢?

  • 对于两个同层级view之间的约束关系,添加到它们的父view上,例如上边例子中,约束添加到self.view上

    图片来自cocoachina
  • 对于两个不同层级view之间的约束关系,添加到他们最近的共同父view上

    图片来自cocoachina
  • 对于有层次关系的两个view之间的约束关系,添加到层次较高的父view上

  • 对于比如长宽之类的,只作用在该view自己身上的话,添加到该view自己上

几个关于AutoLayout值得一看的Blog

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

推荐阅读更多精彩内容