「OC」AutoLayOut的布局学习
文章目录
导入
不知道大家在学习UI的时候,有没有常常因为每次都要不断地通过调整frame的数据,调节视图的位置而头疼?是否会因为当屏幕发生横屏等变化时,我们精心编排的布局又会被打乱而烦躁?如果你也为这些问题而苦恼——那么学习AutoLayOut想必会给你带来很大的帮助。
在笔者学习自定义cell的时候,发现在布局自定义cell的控件时,需要将非常详细的计算控件的相关位置,我们在frame之中确定控件之中的位置,这样做的话不仅十分的麻烦,而且当我们的视图发生外部变化或者内部变化的时候,我们都需要进行布局的重新改变。
常见的外部改变如下:
- 用户调整窗口大小(OS X)。
- 用户在iPad(iOS)上进入或离开分屏浏览。
- 设备旋转(iOS)。
- 活动通话和音频录制条出现或消失(iOS)。
- 您希望支持不同大小的类。
- 您想要支持不同的屏幕尺寸。
内部变化如下:
- 应用程序显示的内容会发生变化。
- 该应用程序支持国际化。(在翻译的时候文本需要不同的空间)
- 该应用程序支持动态类型(iOS)
当我们出现此变化的时候,如果使用AutoLayOut,即我们称之为的自动布局,就方便我们动态地对视图之中的控件进行自动布局,适应不同的屏幕,不同的类等情况,对显示的美观又着十分大的帮助。
创建自定布局的步骤
使用自定义布局,其实本质上就是为布局创建一定的约束(constraint),让系统的自行计算其布局的相对位置。
那么如何创建我们所谓的constraint呢?
- 首先创建约束对象 NSLayoutConstraint
- 而后将约束对象加入到父视图之中
自动布局属性
在学习设置Constraint之前,我们还需要学习自动布局的相关属性,只有我们全部了解这些属性之后,我们才能够将控件进行正确的布局
以上是对于自动布局的相关属性的具象化表述,我们可以在xcode查看其,NSLayoutAttribute
的枚举属性
typedef NS_ENUM(NSInteger, NSLayoutAttribute) {
NSLayoutAttributeLeft = 1,//视图的左边缘
NSLayoutAttributeRight,//视图的右边缘。
NSLayoutAttributeTop,//视图的顶部边缘
NSLayoutAttributeBottom,//视图的底部边缘
NSLayoutAttributeLeading,//视图的前导边缘(根据用户界面布局方向的不同,可能是左边或右边)。
NSLayoutAttributeTrailing,//视图的尾随边缘(根据用户界面布局方向的不同,可能是右边或左边)。
NSLayoutAttributeWidth,//视图的宽度
NSLayoutAttributeHeight,//视图的高度。
NSLayoutAttributeCenterX,//视图的水平中心
NSLayoutAttributeCenterY,//视图的垂直中心
NSLayoutAttributeLastBaseline,//视图的最后一行基线。7
//略去中间部分较为少用的部分。。。
#endif
NSLayoutAttributeNotAnAttribute = 0//NSLayoutAttributeNotAnAttribute表示不是有效的布局属性,用于表示缺少约束或无效的约束。
};
实现自动布局约束的方程
视图层次结构的布局被定义为一系列线性方程。每个约束代表一个单一的方程。你的目标是声明一系列方程,其中只有一个可能的解决方案。
一般而言,约束方程的形式如下:
item1.attribute1 = multiplier × item2.attribute2 + constant
我们用一个例子来说明
- item1:方程中的第一个项目——在这种情况下,是红色视图。该项目必须是视图或布局指南。
- attribute1:第一个项目上要约束的属性——在这种情况下,红色视图的前缘。
- relationship:左右双方之间的关系。这种关系可以有三个值之一:相等,大于或相等,或小于或相等。在这种情况下,左侧和右侧是相等的。
- multiplier:属性2的值乘以这个浮点数。在这种情况下,乘数是
1.0
。 - item2:等式中的第二项——在这种情况下,是蓝色视图。与第一项不同,这可以留空。
- attribute2:第二个项目上要约束的属性——在这种情况下,是蓝色视图的后缘。如果第二个项目留空,这必须
Not an Attribute
。 - constant:一个恒定的浮点偏移量——在这种情况下,
8.0
。该值被添加到属性2的值中。
定位锚的两种方式
在实现自动布局之前,我们要先关闭系统 Autoresizing
代码如下
View1.translatesAutoresizingMaskIntoConstraints = NO;
前面其实我们已经了解了,所谓的约束其实可以等价于一个线性的方程,那么在程序之中我们又该怎么运用所谓的约束去实现自动布局呢?
约束表达式使用Visual Format Language (VFL)或NSLayoutConstraint类来定义。
NSLayoutConstraint类的构造方法
我们来学习如何根据约束表达式来写出对应的NSLayoutConstraint类:
当使用NSLayoutConstraint
类来表示约束表达式时,可以将表达式中的每个部分对应到NSLayoutConstraint
类的构造方法的参数。
假设我们有一个视图view1
,需要将其左侧边缘与父视图的左侧边缘保持20个点的间距,并且宽度为父视图宽度的一半减去10个点。以下是表达式和代码的对应关系:
表达式:view1.left = parentView.left + 20
代码:
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:parentView
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:20.0];
[NSLayoutConstraint activateConstraints:@[leftConstraint]];
表达式:view1.width = parentView.width / 2 - 10
代码:
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:parentView
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:-10.0];
[NSLayoutConstraint activateConstraints:@[widthConstraint]];
在上面的代码中,我们使用constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:
方法来创建约束对象。view1
是约束的第一个项,parentView
是约束的第二个项。NSLayoutAttributeLeft
表示左侧边缘,NSLayoutAttributeWidth
表示宽度。NSLayoutRelationEqual
表示相等关系。multiplier
参数用于设置宽度的比例关系,constant
参数用于设置约束的常量值。
最后,我们使用[NSLayoutConstraint activateConstraints:]
方法将约束添加到视图中,使其生效。
通过这种方式,您可以将约束表达式转换为使用NSLayoutConstraint
类的构造方法来创建相应的约束对象,并将其应用于视图。
以上的约束关系的符号都我们都是使用NSLayoutRelation
枚举来表示约束的关系
- 大于等于(Greater than or equal to):使用
NSLayoutRelationGreaterThanOrEqual
。 - 小于等于(Less than or equal to):使用
NSLayoutRelationLessThanOrEqual
。 - 等于(equal to):使用NSLayoutRelationEqual。
下面是使用NSLayoutConstraint
类的构造方法来表达大于等于和小于等于关系的示例:
// 创建一个宽度至少为100的约束
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:100.0];
// 创建一个高度至多为200的约束
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationLessThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:200.0];
// 将约束添加到视图
[view addConstraint:widthConstraint];
[view addConstraint:heightConstraint];
在上面的示例中,widthConstraint
表示视图的宽度至少为100,使用的关系是NSLayoutRelationGreaterThanOrEqual
。heightConstraint
表示视图的高度至多为200,使用的关系是NSLayoutRelationLessThanOrEqual
。
请注意,使用constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:
方法时,如果第二个参数和第四个参数传入的是nil
,那么attribute
参数和toAttribute
参数应该设置为NSLayoutAttributeNotAnAttribute
。这是因为在这种情况下,约束是与一个固定的常量值相关联,而不是与另一个视图的属性相关联。
Visual Format Language (VFL)的用法
VFL提供了一种简洁的方式来定义约束。它使用ASCII字符串来描述视图之间的关系和布局规则。
一个字符串,具有一定的格式,不同的格式代表不同的约束,并且,一个字符串往往能一次性表达出多个约束
如何写VFL字符串?
以下是苹果官网给出的可视化格式约束示例。
-
标准空间
[button]-[textField]
-
宽度约束
[button(>=50)]
-
连接到Superview
|-50-[purpleBox]-50-|
-
垂直布局
V:[topField]-10-[bottomField]
-
连接视图
[maroonView][blueView]
-
优先权
[button(100@20)]
-
等宽
[button1(==button2)]
-
多个谓词
[flexibleButton(>=70,<=100)]
-
完整的布局线
|-[find]-[findNext]-[findField(>=20)]-|
以下是对VFL的语法的部分总结:
-
文字:button1和button2之间的间距为30
VFL:[button1]-30-[button]
-
文字:button1的宽度为50
VFL:[button1(==50)] 或 [button1(50)]
-
文字:button1 距离 左边20,button2距离右边20, button1和button2间隔10, button1宽度和button2的宽度相等
VFL: |-20-[button1(button2)]-10-[button2(button1)]-20-|
-
文字:button1 距离顶部 为20
VFL: V:|-20-[button1]
-
文字:button1 和 button2 垂直方向间隔10
VFL: V:[button1]-10-[button2]
VFL 特殊符号的含义
|
代表父视图的边缘
H:|
代表父试图的左边缘
V:|
代表父试图的上边缘
[]
代表一个子视图
()
代表一个宽度或高度的条件
-x-
代表间距是 x
-
代表标准间距 8
AutoLayOut在cell之中的运用
OK,我们已经学会了AutoLayOut的相关用法,我们可以将自动布局用在cell之中。
那我自己写的自定义cell来举例,我直接展示cell之中的.m文件,.h文件较为简单,此处忽略
#import "UserInfoTableViewCell.h"
@implementation UserInfoTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self setupViews];
}
return self;
}
- (void)setupViews {
self.avatarImageView = [[UIImageView alloc] init];
self.avatarImageView.translatesAutoresizingMaskIntoConstraints = NO;
self.avatarImageView.layer.cornerRadius = 5;
self.avatarImageView.clipsToBounds = YES;
[self.contentView addSubview:self.avatarImageView];
self.nameLabel = [[UILabel alloc] init];
self.nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.nameLabel.font = [UIFont boldSystemFontOfSize:18];
[self.contentView addSubview:self.nameLabel];
self.wechatIDLabel = [[UILabel alloc] init];
self.wechatIDLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.wechatIDLabel.font = [UIFont systemFontOfSize:14];
self.wechatIDLabel.textColor = [UIColor grayColor];
[self.contentView addSubview:self.wechatIDLabel];
//contentView在这里就是cell视图
[NSLayoutConstraint activateConstraints:@[
[self.avatarImageView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:15],
[self.avatarImageView.centerYAnchor constraintEqualToAnchor:self.contentView.centerYAnchor],
[self.avatarImageView.widthAnchor constraintEqualToConstant:60],
[self.avatarImageView.heightAnchor constraintEqualToConstant:60],
[self.nameLabel.leadingAnchor constraintEqualToAnchor:self.avatarImageView.trailingAnchor constant:15],
[self.nameLabel.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:15],
[self.wechatIDLabel.leadingAnchor constraintEqualToAnchor:self.nameLabel.leadingAnchor],
[self.wechatIDLabel.topAnchor constraintEqualToAnchor:self.nameLabel.bottomAnchor constant:5]
]];
}
@end
—————————————————————————————————————————————————————————————————————————————————————————————————————————
#import "MenuItemTableViewCell.h"
@implementation MenuItemTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self setupViews];
}
return self;
}
- (void)setupViews {
self.iconImageView = [[UIImageView alloc] init];
self.iconImageView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.iconImageView];
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.titleLabel];
[NSLayoutConstraint activateConstraints:@[
[self.iconImageView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:15],
[self.iconImageView.centerYAnchor constraintEqualToAnchor:self.contentView.centerYAnchor],
[self.iconImageView.widthAnchor constraintEqualToConstant:24],
[self.iconImageView.heightAnchor constraintEqualToConstant:24],
[self.titleLabel.leadingAnchor constraintEqualToAnchor:self.iconImageView.trailingAnchor constant:15],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.contentView.centerYAnchor],
[self.titleLabel.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-15]
]];
}
@end
在程序之中我们使用,activateConstraints创建NSLayoutConstraint数组来进行自动布局的创建
在UserInfoTableViewCell
之中:
self.avatarImageView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 15)
:这个约束方程表示self.avatarImageView
的 leading 边距与self.contentView
的 leading 边距相等,并添加一个常量值 15。self.avatarImageView.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor)
:这个约束方程表示self.avatarImageView
的 centerY 边距与self.contentView
的 centerY 边距相等。self.avatarImageView.widthAnchor.constraint(equalToConstant: 60)
:这个约束方程表示self.avatarImageView
的宽度设置为一个常量值 60。self.avatarImageView.heightAnchor.constraint(equalToConstant: 60)
:这个约束方程表示self.avatarImageView
的高度设置为一个常量值 60。self.nameLabel.leadingAnchor.constraint(equalTo: self.avatarImageView.trailingAnchor, constant: 15)
:这个约束方程表示self.nameLabel
的 leading 边距与self.avatarImageView
的 trailing 边距相等,并添加一个常量值 15。self.nameLabel.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 15)
:这个约束方程表示self.nameLabel
的 top 边距与self.contentView
的 top 边距相等,并添加一个常量值 15。self.wechatIDLabel.leadingAnchor.constraint(equalTo: self.nameLabel.leadingAnchor)
:这个约束方程表示self.wechatIDLabel
的 leading 边距与self.nameLabel
的 leading 边距相等。self.wechatIDLabel.topAnchor.constraint(equalTo: self.nameLabel.bottomAnchor, constant: 5)
:这个约束方程表示self.wechatIDLabel
的 top 边距与self.nameLabel
的 bottom 边距相等,并添加一个常量值 5。
在MenuItemTableViewCell.h
也是类似的,读者有兴趣可以观察源代码,理解其相关布局。
接下来我们来看看自定义cell的样子:
总结
自动布局(AutoLayout)是一种用于iOS开发中视图布局的技术,它可以根据设备的屏幕尺寸和方向自动调整视图的位置和大小。下面是关于AutoLayout布局学习的一些总结:
-
创建自定义布局的步骤:使用AutoLayout创建自定义布局的基本步骤包括:创建视图,添加视图到父视图,为视图添加约束。
-
自动布局属性:AutoLayout使用约束来描述视图之间的关系。常用的约束属性包括:宽度(width)、高度(height)、水平位置(leading、trailing、centerX等)、垂直位置(top、bottom、centerY等)、间距(margin等)等。
-
实现自动布局约束的方程:AutoLayout通过线性方程组来解决视图布局的问题。方程的形式为:视图属性1 = 视图属性2 * multiplier + constant。通过设置不同的属性和参数,可以实现各种复杂的布局效果。
-
定位锚的两种方式:在AutoLayout中,可以使用视图的边界(edge)或中心点(center)作为定位锚点。边界锚点包括左边界(leading)、右边界(trailing)、顶部边界(top)、底部边界(bottom)等;中心锚点包括水平中心(centerX)和垂直中心(centerY)。
-
NSLayoutConstraint类的构造方法:NSLayoutConstraint是AutoLayout的核心类之一,用于创建和管理约束。它有多个构造方法,可以根据需要选择适合的方法来创建约束对象。
-
Visual Format Language (VFL)的用法:VFL是一种简洁的语言,用于描述视图之间的约束关系。通过VFL,可以用一种更紧凑的方式编写约束代码,提高代码的可读性和可维护性。
-
AutoLayout在cell中的运用:AutoLayout在表格视图(UITableView)或集合视图(UICollectionView)的单元格(cell)中广泛使用。通过使用AutoLayout,可以实现自动调整单元格的大小和位置,以适应不同的设备和内容。