本系列第一篇文章介绍了自动布局的基本原理,第二篇通过一个简单的例子演示了如何使用IB以可视化方式创建自动布局约束,第三篇使用代码直接创建NSLayoutConstraint实例来定义自动布局约束。本篇文章在第三篇文章的基础上,使用Visual Format Language(暂且翻译为可视化格式语言,简称VFL)创建约束。
在第三篇文章中,我们仅仅创建了4个视图,就需要创建将近20个NSLayoutConstraint实例,而且每次创建NSLayoutConstraint实例时都需要传入7个参数(firstItem.firstAttribute {==,<=,>=} secondItem.secondAttribute * multiplier + constant),非常繁琐而且容易出错。在实际项目中,视图的层次会更复杂,约束的数量就会成倍增加,有没有办法既直观又简单地创建约束?那你不妨试试VFL,这也是这篇文章的主题。
VFL的使用非常简单直观,不过既然是一门语言,必然就有其语法要求。。。
好吧,我们还是以之前的例子边做边讲吧。
打开Xcode,新建项目,选择iOS -> Application -> Single View Application。项目命名为AutoLayoutByVFL,语言任意选择(本篇文章使用的是Objective-C),设备选择Universal。下载苹果Logo图片apple.jpg,并将其拖入项目中。文件下载地址:
http://yunpan.cn/cfmJB82dfSwf6(提取码:4049)
界面上方用来显示苹果Logo图片的是一个UIImageView,其具有如下4个约束:
- logoImageView左侧与父视图左侧对齐
- logoImageView右侧与父视图右侧对齐
- logoImageView顶部与父视图顶部对齐
- logoImageView高度为父视图高度一半
将ViewController类的viewDidLoad方法修改如下:
- (void)viewDidLoad
{
[super viewDidLoad];
UIImageView* logoImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"apple.jpg"]];
logoImageView.translatesAutoresizingMaskIntoConstraints = NO;
logoImageView.contentMode = UIViewContentModeScaleAspectFit;
[self.view addSubview:logoImageView];
//水平方向上,logoImageView左侧与父视图左侧对齐,logoImageView右侧与父视图右侧对齐
NSArray* hConstraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[logoImageView]-0-|" options:0 metrics:nil views:@{@"logoImageView": logoImageView}];
[NSLayoutConstraint activateConstraints:hConstraintArray];
//垂直方向上,logoImageView顶部与父视图顶部对齐
NSArray* vConstraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[logoImageView]" options:0 metrics:nil views:@{@"logoImageView": logoImageView}];
[NSLayoutConstraint activateConstraints:vConstraintArray];
//logoImageView高度为父视图高度一半
NSLayoutConstraint* heightConstraint = [NSLayoutConstraint constraintWithItem:logoImageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:0.5f constant:0.0f];
heightConstraint.active = YES;
}
viewDidLoad方法首先调用了NSLayoutConstraint类的一个静态方法constraintsWithVisualFormat:…..,根据传入的VFL字符串生成若干约束,该方法定义如下:
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views;
参数format是一个符合VFL语法的字符串。上述代码中的H:表示本VFL字符串描述的是水平方向的约束,与之相对的是V:表示垂直方向。如果VFL字符串没有指明H:还是V:,则默认为水平方向。|表示父视图。VFL要求所有视图的名字必须放在中括号之内,[logoImageView]指代的就是logoImageView。-0-表示的是间距值为0。
所以@”H:|-0-[logoImageView]-0-|”表示在水平方向上,logoImageView左侧与其父视图左侧的间距为0,logoImageView右侧与其父视图右侧的间距为0。@”V:|-0-[logoImageView]”表示在垂直方向上,logoImageView顶部与其父视图顶部的间距为0。
另外说一句,-0-可以不写,也就是说如果间距为0则不用明确写出,所以@”H:|-0-[logoImageView]-0-|”可以精简为@”H:|[logoImageView]|”,@”V:|-0-[logoImageView]”可以精简为@”V:|[logoImageView]”,是不是很直观?
参数opts表示布局的对齐方式与方向:
typedef NS_OPTIONS(NSUInteger, NSLayoutFormatOptions)
{
NSLayoutFormatAlignAllLeft = (1 << NSLayoutAttributeLeft),
NSLayoutFormatAlignAllRight = (1 << NSLayoutAttributeRight),
NSLayoutFormatAlignAllTop = (1 << NSLayoutAttributeTop),
NSLayoutFormatAlignAllBottom = (1 << NSLayoutAttributeBottom),
NSLayoutFormatAlignAllLeading = (1 << NSLayoutAttributeLeading),
NSLayoutFormatAlignAllTrailing = (1 << NSLayoutAttributeTrailing),
NSLayoutFormatAlignAllCenterX = (1 << NSLayoutAttributeCenterX),
NSLayoutFormatAlignAllCenterY = (1 << NSLayoutAttributeCenterY),
NSLayoutFormatAlignAllBaseline = (1 << NSLayoutAttributeBaseline),
NSLayoutFormatAlignAllLastBaseline = NSLayoutFormatAlignAllBaseline,
NSLayoutFormatAlignAllFirstBaseline NS_ENUM_AVAILABLE_IOS(8_0) = (1 << NSLayoutAttributeFirstBaseline),
NSLayoutFormatAlignmentMask = 0xFFFF,
/* choose only one of these three */
NSLayoutFormatDirectionLeadingToTrailing = 0 << 16, // default
NSLayoutFormatDirectionLeftToRight = 1 << 16,
NSLayoutFormatDirectionRightToLeft = 2 << 16,
NSLayoutFormatDirectionMask = 0x3 << 16,
};
参数metrics是一个字典。其中的键(Key)是字符串,出现在VFL语句中;值(Value)是NSNumber对象。在解析VFL字符串时,编译器将自动把键替换为其对应的值,目的是使VFL更容易明白和理解。
另外,在解析VFL时,UIKit需要知道VFL字符串中的视图名称究竟对应哪个真实的视图,视图映射字典参数views就用来提供这个信息。其中的键(Key)是字符串,出现在VFL语句中;值(Value)是UIView对象。上述代码中的@{@”logoImageView”: logoImageView}表示的就是字符串@”logoImageView”对应视图logoImageView。
不过有个很遗憾的事实要告诉你,VFL并不能表达所有的约束,例如“logoImageView高度为父视图高度一半”这样的具有比例关系的约束,就无法使用VFL表达出来,所以这时我们只能直接创建NSLayoutConstraint实例了,就像上面的代码那样。
接着我们添加UIScrollView,在viewDidLoad方法中添加如下代码:
UIScrollView* scrollView = [UIScrollView new];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.backgroundColor = [UIColor blueColor]; //为了方便查看效果,暂时将scrollView背景色设置为蓝色
[self.view addSubview:scrollView];
//水平方向上,scrollView左侧与父视图左侧对齐,scrollView右侧与父视图右侧对齐
NSArray* hScrollViewConstraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(scrollView)];
[NSLayoutConstraint activateConstraints:hScrollViewConstraintArray];
//垂直方向上,scrollView顶部与logoImageView底部对齐,scrollView底部与父视图底部对齐
NSArray* vScrollViewConstraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[logoImageView][scrollView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(logoImageView, scrollView)];
[NSLayoutConstraint activateConstraints:vScrollViewConstraintArray];
需要额外说明的是,这里调用了一个NSDictionaryOfVariableBindings宏,它能够方便我们构建字典参数。简单说来,NSDictionaryOfVariableBindings(scrollView)就等于@{@”scrollView”: scrollView},NSDictionaryOfVariableBindings(logoImageView, scrollView)就等于@{@”logoImageView”: logoImageView, @”scrollView”: scrollView}。
另外,在垂直方向上,我们可以把之前的@”V:|[logoImageView]”与刚才的@”V:[logoImageView][scrollView]|”合并为一句@”V:|[logoImageView][scrollView]|”,就不需要分别创建heightConstraint与vScrollViewConstraintArray了,达到进一步精简的目的。
接着我们添加scrollView中的两个UILabel对象,在viewDidLoad方法中添加如下代码:
UILabel* nameLabel = [UILabel new];
nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
nameLabel.text = @"苹果公司";
nameLabel.backgroundColor = [UIColor greenColor];
[scrollView addSubview:nameLabel];
UILabel* descriptionLabel = [UILabel new];
descriptionLabel.translatesAutoresizingMaskIntoConstraints = NO;
descriptionLabel.text = @"苹果公司(Apple Inc. )是美国的一家高科技公司。由史蒂夫·乔布斯、斯蒂夫·沃兹尼亚克和罗·韦恩(Ron Wayne)等三人于1976年4月1日创立,并命名为美国苹果电脑公司(Apple Computer Inc. ), 2007年1月9日更名为苹果公司,总部位于加利福尼亚州的库比蒂诺。\n苹果公司创立之初主要开发和销售的个人电脑,截至2014年致力于设计、开发和销售消费电子、计算机软件、在线服务和个人计算机。苹果的Apple II于1970年代助长了个人电脑革命,其后的Macintosh接力于1980年代持续发展。该公司硬件产品主要是Mac电脑系列、iPod媒体播放器、iPhone智能手机和iPad平板电脑;在线服务包括iCloud、iTunes Store和App Store;消费软件包括OS X和iOS操作系统、iTunes多媒体浏览器、Safari网络浏览器,还有iLife和iWork创意和生产力套件。苹果公司在高科技企业中以创新而闻名世界。\n苹果公司1980年12月12日公开招股上市,2012年创下6235亿美元的市值记录,截至2014年6月,苹果公司已经连续三年成为全球市值最大公司。苹果公司在2014年世界500强排行榜中排名第15名。2013年9月30日,在宏盟集团的“全球最佳品牌”报告中,苹果公司超过可口可乐成为世界最有价值品牌。2014年,苹果品牌超越谷歌(Google),成为世界最具价值品牌 。";
descriptionLabel.numberOfLines = 0;
descriptionLabel.backgroundColor = [UIColor yellowColor];
[scrollView addSubview:descriptionLabel];
//水平方向上,nameLabel左侧与父视图左侧对齐,nameLabel右侧与父视图右侧对齐,nameLabel宽度与logoImageView宽度相同
NSArray* hNameLabelConstraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[nameLabel(==logoImageView)]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(nameLabel, logoImageView)];
[NSLayoutConstraint activateConstraints:hNameLabelConstraintArray];
//水平方向上,descriptionLabel左侧与父视图左侧对齐,descriptionLabel右侧与父视图右侧对齐,descriptionLabel宽度与logoImageView宽度相同
NSArray* hDescriptionLabelConstraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[descriptionLabel(==logoImageView)]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel, logoImageView)];
[NSLayoutConstraint activateConstraints:hDescriptionLabelConstraintArray];
//垂直方向上,nameLabel顶部与父视图顶部对齐,nameLabel高度为20,nameLabel底部与descriptionLabel顶部对齐,descriptionLabel底部与父视图底部对齐
NSArray* vLabelConstraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[nameLabel(20)][descriptionLabel]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(nameLabel, descriptionLabel)];
[NSLayoutConstraint activateConstraints:vLabelConstraintArray];
其中@”H:|[nameLabel(==logoImageView)]|”表示nameLabel宽度与logoImageView宽度相等,@”V:|[nameLabel(20)][descriptionLabel]|”表示nameLabel的宽度为20。到此我们就完成了这个例子,在此附上全部代码:
- (void)viewDidLoad
{
[super viewDidLoad];
UIImageView* logoImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"apple.jpg"]];
logoImageView.translatesAutoresizingMaskIntoConstraints = NO;
logoImageView.contentMode = UIViewContentModeScaleAspectFit;
[self.view addSubview:logoImageView];
UIScrollView* scrollView = [UIScrollView new];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:scrollView];
//水平方向上,logoImageView左侧与父视图左侧对齐,logoImageView右侧与父视图右侧对齐
NSArray* hConstraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[logoImageView]|" options:0 metrics:nil views:@{@"logoImageView": logoImageView}];
[NSLayoutConstraint activateConstraints:hConstraintArray];
//垂直方向上,logoImageView顶部与父视图顶部对齐,logoImageView底部与scrollView顶部对齐,scrollView底部与父视图底部对齐
NSArray* vConstraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[logoImageView][scrollView]|" options:0 metrics:nil views:@{@"logoImageView": logoImageView, @"scrollView": scrollView}];
[NSLayoutConstraint activateConstraints:vConstraintArray];
//logoImageView高度为父视图高度一半
NSLayoutConstraint* heightConstraint = [NSLayoutConstraint constraintWithItem:logoImageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:0.5f constant:0.0f];
heightConstraint.active = YES;
//水平方向上,scrollView左侧与父视图左侧对齐,scrollView右侧与父视图右侧对齐
NSArray* hScrollViewConstraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(scrollView)];
[NSLayoutConstraint activateConstraints:hScrollViewConstraintArray];
UILabel* nameLabel = [UILabel new];
nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
nameLabel.text = @"苹果公司";
nameLabel.backgroundColor = [UIColor greenColor];
[scrollView addSubview:nameLabel];
UILabel* descriptionLabel = [UILabel new];
descriptionLabel.translatesAutoresizingMaskIntoConstraints = NO;
descriptionLabel.text = @"苹果公司(Apple Inc. )是美国的一家高科技公司。由史蒂夫·乔布斯、斯蒂夫·沃兹尼亚克和罗·韦恩(Ron Wayne)等三人于1976年4月1日创立,并命名为美国苹果电脑公司(Apple Computer Inc. ), 2007年1月9日更名为苹果公司,总部位于加利福尼亚州的库比蒂诺。\n苹果公司创立之初主要开发和销售的个人电脑,截至2014年致力于设计、开发和销售消费电子、计算机软件、在线服务和个人计算机。苹果的Apple II于1970年代助长了个人电脑革命,其后的Macintosh接力于1980年代持续发展。该公司硬件产品主要是Mac电脑系列、iPod媒体播放器、iPhone智能手机和iPad平板电脑;在线服务包括iCloud、iTunes Store和App Store;消费软件包括OS X和iOS操作系统、iTunes多媒体浏览器、Safari网络浏览器,还有iLife和iWork创意和生产力套件。苹果公司在高科技企业中以创新而闻名世界。\n苹果公司1980年12月12日公开招股上市,2012年创下6235亿美元的市值记录,截至2014年6月,苹果公司已经连续三年成为全球市值最大公司。苹果公司在2014年世界500强排行榜中排名第15名。2013年9月30日,在宏盟集团的“全球最佳品牌”报告中,苹果公司超过可口可乐成为世界最有价值品牌。2014年,苹果品牌超越谷歌(Google),成为世界最具价值品牌 。";
descriptionLabel.numberOfLines = 0;
descriptionLabel.backgroundColor = [UIColor yellowColor];
[scrollView addSubview:descriptionLabel];
//水平方向上,nameLabel左侧与父视图左侧对齐,nameLabel右侧与父视图右侧对齐,nameLabel宽度与logoImageView宽度相同
NSArray* hNameLabelConstraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[nameLabel(==logoImageView)]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(nameLabel, logoImageView)];
[NSLayoutConstraint activateConstraints:hNameLabelConstraintArray];
//水平方向上,descriptionLabel左侧与父视图左侧对齐,descriptionLabel右侧与父视图右侧对齐,descriptionLabel宽度与logoImageView宽度相同
NSArray* hDescriptionLabelConstraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[descriptionLabel(==logoImageView)]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel, logoImageView)];
[NSLayoutConstraint activateConstraints:hDescriptionLabelConstraintArray];
//垂直方向上,nameLabel顶部与父视图顶部对齐,nameLabel高度为20,nameLabel底部与descriptionLabel顶部对齐,descriptionLabel底部与父视图底部对齐
NSArray* vLabelConstraintArray = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[nameLabel(20)][descriptionLabel]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(nameLabel, descriptionLabel)];
[NSLayoutConstraint activateConstraints:vLabelConstraintArray];
}
与第三篇文章中逐个去创建NSLayoutConstraint对象相比,是不是简单直观了不少?程序最终项目文件链接:
http://yunpan.cn/cVE8i8WmnpJwv(提取码:d348)
本篇文章我们初步了解了VFL的基本使用方式,关于VFL的具体语法格式请参看苹果的《Auto Layout Guide》,链接:
在本系列后续文章中,我将继续介绍自动布局中的动画实现、事件处理、调试,以及iOS 8中新添加的Size Class的使用,敬请期待吧。