上一篇文章提到了苹果已经创建了Visual Format language(可视化格式语言),可以用来实现自动布局。
如果有两个按钮你想将它们在水平方向相隔100 像素,你可以使用Visual Format language 代码来表达,如下:
[button1]-100-[button2]Visual Format Language 中的限制条件是通过NSLayoutConstraint 类中的constrainsWithVisualFormat:ptions:metrics:views:的类方法。下面简单解释一下方法中每个参数:
constraintsWithVisualFormat
这是Visual Format Language 的表达式,被写成了NSString。
Options
这是一个NSLayoutFormatOption 类型的参数。对于Visual Format Language,我们通常将这个参数设置为0。
Metrics
一个字典常量值,将会在Visual Format Language 表达式中使用到。为了简单一点,目前我们将会传入nil 值。
Views
这是一个视图字典,你将会在这个方法的第一个参数写限制条件。为了构造这个字典,简单地使用NSDIctionaryOfVaribleBindings C 函数并传递你的视图到这个方法里。它将会为你构造这个字典。在这个字典的关键字是视图的名字,你将会在方法的第一个参数中使用它。
问题
想要定义限制条件来改变一个UI 组件在其父视图的水平和垂直方向布局的方法。
方案
使用方程式里H:方向符号代表水平方向的边距,使用V:方向符号代表垂直方向的边距。
使用方程式里H:方向符号代表水平方向的边距,使用V:方向符号代表垂直方向的边距。
下面有一张图,它能够帮助我们很好地理解Visual Format Language。
为了让程序看起来整齐一致并使设计者更容易做出决定,Apple 已经制定了UI 之间的距离或留空标准。这个标准在Apple 的iOS Human Interface Guidelines 中进行了介绍。
现在我们将要用Visual Format Language来实现下图的布局。
在编辑代码之前,让我们先写一下在图中组件的约束条件:
·邮件区域离视图的顶部具有一个标准的垂直方向距离。
·确认邮件的区域在垂直方向山距邮件区域有一个标准的距离。
·注册按钮距确认邮件区域的垂直方向上具有一个标准的距离。
·所有的组件在水平方向上在其父视图中都是居中的。
·邮件和确认邮件区域在水平方向上距父视图两边都有一个标准的距离。
·按钮的宽度被修改成128 像素。
·邮件区域离视图的顶部具有一个标准的垂直方向距离。
·确认邮件的区域在垂直方向山距邮件区域有一个标准的距离。
·注册按钮距确认邮件区域的垂直方向上具有一个标准的距离。
·所有的组件在水平方向上在其父视图中都是居中的。
·邮件和确认邮件区域在水平方向上距父视图两边都有一个标准的距离。
·按钮的宽度被修改成128 像素。
让我们先从使用
Visual Format Language
在视图控制器顶部定义约束条件开始吧:
- /*Email text Field constraints*/
- NSString *const KEmailTextFieldHorizontal = @"H:|-[_textFieldEmail]-|";
- NSString *const KEmailTextFieldVertical = @"V:|-[_textFieldEmail]";
- /*Confirm email text field constraints */
- NSString *const KConfirmEmailHorizontal = @"H:|-[_textFieldConfirmEmail]-|";
- NSString *const KConfirmEmailVertical = @"V:[_textFieldEmail]-[_textFieldConfirmEmail]";
- /*Redister button constraint */
- NSString *const KRegisterVertical = @"V:[_textFieldConfirmEmail]-[_registerButton]";
很显然两个文本框都有它们自己的水平和垂直方向的 Visual Format Language 定义的限制条件,但是 Register 按钮只有在垂直方向的限制条件。为什么呢?结果表明 UI 组件在水平方向的居中条件不能由 Visual Format Language 定义.任何东西都不是完美的,在上一篇自动布局的文章我们已经学会了如何自动布局按钮的水平约束条件了(忘了的复习一下吧)。
现在让我们在视图控制器的执行文件将我么的UI 组件定义成我们视图控制器的私有属性:
- @interface ViewController ()
- @property(nonatomic,strong) UIButton *button;
- @property (nonatomic,strong)UITextField *textFieldEmail;
- @property (nonatomic,strong)UITextField *textFieldConfirmEmail;
- @property (nonatomic,strong)UIButton *registerButton;
- @end
接下来呢?我们需要在视图控制器的执行文件里构造我们的UI 组件。所以我会写两个方法来帮助完成构造。记住,我们不需要再去设置这些UI 组件的框架了。Auto Layout(自动布局)会帮助我们完成:
- -(UITextField *)textFieldWithPlaceholder:(NSString *)paramPlaceholder{
- UITextField *result = [[UITextField alloc] init];
- result.translatesAutoresizingMaskIntoConstraints = NO;
- result.borderStyle = UITextBorderStyleRoundedRect;
- result.placeholder = paramPlaceholder;
- return result;
- }
- -(void)constructUIComponents
- {
- self.textFieldEmail = [self textFieldWithPlaceholder:@"Email"];
- self.textFieldConfirmEmail =[self textFieldWithPlaceholder:@"Confirm Email"];
- self.registerButton = [UIButton buttonWithType:UIButtonTypeCustom];
- [self.registerButton setBackgroundColor:[UIColor blackColor]];
- self.registerButton.translatesAutoresizingMaskIntoConstraints = NO;
- [self.registerButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
- [self.registerButton setTitle:@"Register" forState:UIControlStateNormal];
- }
上面的两个方法创建了两个文本框和一个按钮。其中translatesAutoresizingMaskIntoConstraints这个属性在上一篇文章中有解释。
我们将构造UI 组件了,但是我们的视图控制器的viewDidLoad 方法需要将这三个UI 组件都添加到我们的视图中,所以问什么不使用一个简短的方法来帮助我们完成呢?
- - (void) addUIComponentsToView:(UIView *)paramView
- {
- [paramView addSubview:self.textFieldEmail];
- [paramView addSubview:self.textFieldConfirmEmail];
- [paramView addSubview:self.registerButton];
- }
在此,接下来的的任务就是创建一些方法,这些方法允许我们构造并收集所有的限制条件到一个数组里。为此,我们有三个方法来返回每个UI 组件的限制条件到一个数组里。
我们还有一个方法,它将收集从这三个UI 组件返回的所有限制条件并将它们放进一个大的数组里。下面是我们如何实现它的:
我们还有一个方法,它将收集从这三个UI 组件返回的所有限制条件并将它们放进一个大的数组里。下面是我们如何实现它的:
- - (NSArray *) emailTextFieldConstraints
- {
- NSMutableArray *result = [[NSMutableArray alloc] init];
- NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_textFieldEmail);//??
- [result addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:KEmailTextFieldHorizontal
- options:0
- metrics:nil
- views:viewsDictionary]];
- [result addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:KEmailTextFieldVertical
- options:0
- metrics:nil
- views:viewsDictionary]];
- return [NSArray arrayWithArray:result];
- }
- -(NSArray *)confirmEmailTextFieldConstraints
- {
- NSMutableArray *result = [[NSMutableArray alloc] init];
- NSDictionary *viewDictionary = NSDictionaryOfVariableBindings(_textFieldConfirmEmail,_textFieldEmail);
- [result addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:KConfirmEmailHorizontal
- options:0
- metrics:nil
- views:viewDictionary]];
- [result addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:KConfirmEmailVertical
- options:0
- metrics:nil
- views:viewDictionary]];
- return [NSArray arrayWithArray:result];
- }
- - (NSArray *) registerButtonConstraints{
- NSMutableArray *result = [[NSMutableArray alloc] init];
- NSDictionary *viewsDictionary =
- NSDictionaryOfVariableBindings(_registerButton, _textFieldConfirmEmail);
- [result addObject:
- [NSLayoutConstraint constraintWithItem:self.registerButton
- attribute:NSLayoutAttributeCenterX
- relatedBy:NSLayoutRelationEqual
- toItem:self.view
- attribute:NSLayoutAttributeCenterX
- multiplier:1.0f
- constant:0.0f]
- ];
- [result addObjectsFromArray:
- [NSLayoutConstraint constraintsWithVisualFormat:KRegisterVertical
- options:0
- metrics:nil
- views:viewsDictionary]
- ];
- return [NSArray arrayWithArray:result];
- }
- - (NSArray *) constraints
- {
- NSMutableArray *result = [[NSMutableArray alloc] init];
- [result addObjectsFromArray:[self emailTextFieldConstraints]];
- [result addObjectsFromArray:[self confirmEmailTextFieldConstraints]];
- [result addObjectsFromArray:[self registerButtonConstraints]];
- return [NSArray arrayWithArray:result];
- }
这里需要注意一下,
constraintsWithVisualFormat:options:metrics:views:方法和
constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:方法返回的结果类型是不一样的,前者返回一个数组,后者返回的是一个id类型对象。
实际上这是视图控制器的限制条件实例方法,它将收集三个
UI 组件的所有限制条件并将其返回到一个大的数组里。现在到了控制器的主要部分了,就是这个viewDidLoad 方法:
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- [self constructUIComponents];
- [self addUIComponentsToView:self.view];
- [self.view addConstraints:[self constraints]];
- }
- /* Suport rotation of device to all orientation */
- -(NSUInteger)supportedInterfaceOrientations
- {
- return UIInterfaceOrientationMaskAll;
- }
这个方法简单的构造了这个UI,使用我们之前写的方法添加UI 组件和它们的限制条件到它们自身。最后,运行一下吧,然后旋转一下模拟器看看是不是符合要求。
上面的例子比较简单,几个view的父视图是相同,他们的约束均添加给父视图即可。其它情况的添加规则可以查看上一篇文章,有详细描述添加规则。