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 *)metrics
                                                              views:(NSDictionary *)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];

运行代码:

AutoLayout-使用代码进行适配_第1张图片
未设置translatesAutoresizingMaskIntoConstraints属性时打印信息

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

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

运行程序:

AutoLayout-使用代码进行适配_第2张图片
约束方式一 竖屏
AutoLayout-使用代码进行适配_第3张图片
约束方式一 横屏

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

设置约束:使用方法二

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

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)]"不就行了么,但是实际可以么?修改之后运行发现程序崩溃,也就是说系统不识别这种设置约束的方法

AutoLayout-使用代码进行适配_第4张图片
设置[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上

    AutoLayout-使用代码进行适配_第5张图片
    图片来自cocoachina
  • 对于两个不同层级view之间的约束关系,添加到他们最近的共同父view上

    AutoLayout-使用代码进行适配_第6张图片
    图片来自cocoachina
  • 对于有层次关系的两个view之间的约束关系,添加到层次较高的父view上

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

几个关于AutoLayout值得一看的Blog

  • AutoLayout苹果官方文档
  • VFL苹果官方说明文档
  • 学习AutoLayout(VFL)
  • 史上比较用心的纯代码实现 AutoLayout
  • 使用Auto Layout中的VFL(Visual format language)--代码实现自动布局
  • AutoLayout视频学习资料,需要的请给我留言要密码
为了学习AutoLayout,笔者最近公司需求做了个小Demo,Github地址

你可能感兴趣的:(AutoLayout-使用代码进行适配)