`

iOS AutoLayout 基础--Visual Format Language

阅读更多

转自:http://commandshift.co.uk/blog/2013/01/31/visual-format-language-for-autolayout/

 

Visual Format Language (VFL) allows the concise building of your layout using an ASCII-art type format string. It’s a powerful tool, but above and beyond the official documentation, there isn’t a lot of information out there. This is going to be an entire post about a single method:

+ (NSArray *)constraintsWithVisualFormat:(NSString *)format
                                 options:(NSLayoutFormatOptions)opts
                                 metrics:(NSDictionary *)metrics
                                   views:(NSDictionary *)views

 

A quick diversion

When building a layout in code, all of your views must have translatesAutoresizingMaskIntoConstraints set to NO. This quickly becomes tedious. A quick category on UIView helps here:

@interface UIView (Autolayout)
+(id)autolayoutView;
@end

@implementation UIView (Autolayout)
+(id)autolayoutView
{
    UIView *view = [self new];
    view.translatesAutoresizingMaskIntoConstraints = NO;
    return view;
}
@end

 There is much, much more in this category, which I will be covering in the final article in this series, together with a GitHub repository. For now, we just want the autolayoutViewmethod.

This doesn’t work with views that require initialisation arguments (particularly a UICollectionView, or a UIButton, for those you’ll have to manually set the property) but does help in most cases. Remember, in an autolayout world, initWithFrame: is no more!

views

This argument takes an NSDictionary, which contains each view referred to in the layout string. It can contain additional views as well, if you want to build a single dictionary for your entire view controller and re-use it in multiple calls. You’re likely to have at least two calls to constraintsWithVisualFormat when laying out a view controller, and may have several – each VFL statement represents layout along a single axis, in a single line.

For your convenience, a macro called NSDictionaryOfVariableBindings has also been created. You supply a list of view variable names, and it gives you a dictionary.

Because this was created specifically for NSLayoutConstraint, and indeed is declared in NSLayoutConstraint.h, there is a certain air of magic about it, like you must use this macro for your constraints. Of course this isn’t true. Above the definition of the macro it helpfully states:

NSDictionaryOfVariableBindings(v1, v2, v3) is equivalent to [NSDictionary dictionaryWithObjectsAndKeys:v1, @“v1”, v2, @“v2”, v3, @“v3”, nil];

 In English, it creates a dictionary where the name of your variable is the key, and your variable is the object. For example:

UILabel *label = [UILabel autolayoutView];
NSDictionary *views = NSDictionaryOfVariableBindings(label);

 [views objectForKey:@"label"] will return our label object, and [label] inside a VFL string will refer to the same object.

Some developers don’t create local variables when assigning views, putting everything directly into properties instead. This doesn’t work with the macro. In this case you can build the dictionary manually. You can use anything you like for the keys, as long as it matches what you use in the VFL string.

metrics

This is another dictionary, and is a useful place to centralise any magic numbers you need for your layout. Like the views dictionary, you can include all the metrics you might use or re-use in multiple calls to this method. I’d recommend using a metrics dictionary over entering numbers directly in VFL, as it makes subsequent changes easier. It’s simple enough to do, particularly with Objective-C literals:

NSDictionary *metrics = @{@"buttonWidth":@200.0};

 A simple dictionary with a string key and an NSNumber value. Using this dictionary as the metrics argument means that we can write buttonWidth in a VFL string and it will be automatically substituted with 200.0.

options

The use of the options argument is is not explained clearly in the documentation. It states that you can only use a single one of the options, but this is not the case. For example, in a horizontal layout, it would be quite common to align the tops and bottoms of all of the views in the VFL statement. You can achieve this by passing inNSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom in the options. The values are bitmasks, and appear to be treated as such when processing. You can only apply options in the axis perpendicular to the layout you are using in the VFL string – so if you were doing a horizontal layout, you’d specify vertical sizing or positioning options such as NSLayoutFormatAlignAllTop or NSLayoutFormatAlignAllCenterY, and if you were doing a vertical layout, NSLayoutFormatAlignAllLeft or NSLayoutFormatAlignAllCenterX.

format

This is the meat of the topic. I personally can’t make head or tail of the “Visual Format String Grammar” in the documentation, so here’s my attempt to explain it.

Views are named and enclosed in square brackets: [button] refers to the view stored under the key button in the views dictionary.

The superview is represented by a pipe : | character. All views must have been added to a superview before you can create constraints relating to layout. You will quickly be informed of this at run time if you don’t follow this rule.

Spacing is represented by a dash - character.

You don’t need to try and fit every facet of your layout into a single VFL string. All the method does is parse the VFL, make individual constraints and return them as an array. You have to be expressing at least one constraint though, otherwise the string is meaningless:

  • |-[button]-| : The button should be the standard spacing from the left and right edges of the superview.
  • |-[button] : The button should be the standard spacing from the left edge of the superview.
  • |[button] : The button should be flush to the left edge of the superview.
  • -[button]- : Meaningless. Spacing to what?

Note that the second and third examples don’t appear to give the layout engine enough information to determine the width of the button – this is OK, because a button is smart enough to know its own width based on its title, image and font properties. The same applies for labels and many other built-in controls.

You gain more control over the layout by adding in metrics. Spacing metrics are added by adding in the metric and an additional dash:

  • |-30.0-[button]-30.0-|: The button should be 30.0 points inset from the left and right edges of the superview.

You can replace the number with a key from your metrics dictionary for reusability and DRY purposes:

  • |-spacing-[button]-spacing-| : The button should be spacing points inset from the left and right edges, where spacing is a key in the metrics dictionary.

Sizing metrics are added by placing the number in parentheses after the name of the view:

  • [button(200.0)] : The button must be 200.0 points wide.

As before, this can be replaced with a key from the metrics dictionary:

  • [button(buttonWidth)] : The button must be buttonWidth points wide, where buttonWidth is a key in the metrics dictionary.

Excitingly, it can also be replaced with the name of another view:

  • |-[button1(button2)]-[button2]-| : button1 must be the same width as button2, they have a standard spacing between them, button1 is a standard spacing from the left edge of the superview, and button2 is a standard spacing from the right edge of the superview.

This layout will divide the two buttons evenly across the width of the superview. This is useful on device rotation – your layout will grow or shrink to fit the different orientation.

All of these examples have dealt with horizontal layout, in a left-to-right fashion. For simplicity, I am assuming a left-to-right layout, but this will work for right-to-left layouts as well.

For vertical layouts, simply add V: to the start of the VFL string:

  • V:|-[button(50.0)] : the button must be 50.0 points high and the standard spacing from the top of the superview.

Here’s a practical example. We want three labels along the bottom of our view controller. The three labels must all be the same height and width, and must grow or shrink to fit as the device rotates.

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Create our three labels using the category method

    UILabel *one = [UILabel autolayoutView];
    UILabel *two = [UILabel autolayoutView];
    UILabel *three = [UILabel autolayoutView];

    // Put some content in there for illustrations
    NSInteger labelNumber = 0;
    for (UILabel *label in @[one,two,three])
    {
        label.backgroundColor = [UIColor redColor];
        label.textAlignment = NSTextAlignmentCenter;
        label.text = [NSString stringWithFormat:@"%d",labelNumber++];
        [self.view addSubview:label];
    }

    // Create the views and metrics dictionaries
    NSDictionary *metrics = @{@"height":@50.0};
    NSDictionary *views = NSDictionaryOfVariableBindings(one,two,three);

    // Horizontal layout - note the options for aligning the top and bottom of all views
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-[one(two)]-[two(three)]-[three]-|" options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom metrics:metrics views:views]];

    // Vertical layout - we only need one "column" of information because of the alignment options used when creating the horizontal layout
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[one(height)]-|" options:0 metrics:metrics views:views]];
}

 We have defined the layout in just two VFL statements. Try that with manual frame setting and autoresizing masks.

So far, all of the examples described have had equalities expressed – that is, the button width must be 200. You can also use inequalities – these are arguably more useful on OS X than iOS, where users can’t go changing the size of your interface willy-nilly, but there are uses.

Inequalities are either less-than-or-equal-to (<=) or greater-than-or-equal-to (>=). This comes before the size metric in the VFL string. If you don’t include either, equality is assumed. If you want to be explicit, you can use == to signify equality:

  • V:|-(==padding)-[imageView]->=0-[button]-(==padding)-| :
    • The top of the image view must be padding points from the top of the superview
    • The bottom of the image view must be greater than or equal to 0 points from the top of the button
    • The bottom of the button must be padding points from the bottom of the superview.

This would ensure that the superview can shrink to no more than the height of the padding, image and button, but if it grows larger than that, the image and button will move apart.

Consider this situation. We have a news article app, with a view that shows a headline, author, image and a button. The layout is like this:

The headline can be quite long – it can go over several lines. What do we need to think about for this layout to work?

  • The header view is a fixed width.
  • The header view must adjust its height to fit the contents.
  • The headline’s bottom edge must be no less than zero points above the author information.
  • The headline’s right edge must be inset from the left edge of the image.
  • Headline text should wrap over multiple lines if required.
  • If the headline’s length makes it longer than the height of the button and image together, the gap between the button and the image should grow.
  • If the headline’s length makes it shorter than the height of the button and image together, the gap between the headline and the author information should grow.

In a pre-autolayout world, we’re looking at a world of tedious frame calculations and calls to font sizing methods. Welcome to the future:

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIView *headerView = [UIView autolayoutView];
    headerView.backgroundColor = [UIColor lightGrayColor];
    [self.view addSubview:headerView];

    UILabel *headline = [UILabel autolayoutView];
    headline.font = [UIFont systemFontOfSize:30.0];
    headline.numberOfLines = 0;
    headline.preferredMaxLayoutWidth = 125.0; // This is necessary to allow the label to use multi-line text properly
    headline.backgroundColor = [UIColor yellowColor]; // So we can see the sizing
    headline.text = @"Kitten kills again";
    [headerView addSubview:headline];

    UILabel *byline = [UILabel autolayoutView];
    byline.font = [UIFont systemFontOfSize:14.0];
    byline.backgroundColor = [UIColor greenColor]; // So we can see the sizing
    byline.text = @"Paws McGruff";

    [headerView addSubview:byline];

    UIImageView *imageView = [UIImageView autolayoutView];
    imageView.contentMode = UIViewContentModeScaleAspectFit;
    [headerView addSubview:imageView];

    // Lovely image loaded for example purposes
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        UIImage *kitten = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://placekitten.com/150/150"] options:0 error:nil]];
        dispatch_async(dispatch_get_main_queue(), ^{
            imageView.image = kitten;
        });
    });

    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.translatesAutoresizingMaskIntoConstraints = NO;
    [button setTitle:@"Button" forState:UIControlStateNormal];
    [headerView addSubview:button];

    // Layout

    NSDictionary *views = NSDictionaryOfVariableBindings(headerView,headline,byline,imageView,button);
    NSDictionary *metrics = @{@"imageEdge":@150.0,@"padding":@15.0};

    // Header view fills the width of its superview
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[headerView]|" options:0 metrics:metrics views:views]];
    // Header view is pinned to the top of the superview
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[headerView]" options:0 metrics:metrics views:views]];

    // Headline and image horizontal layout
    [headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-padding-[headline]-padding-[imageView(imageEdge)]-padding-|" options:0 metrics:metrics views:views]];

    // Headline and byline vertical layout - spacing at least zero between the two
    [headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-padding-[headline]->=0-[byline]-padding-|" options:NSLayoutFormatAlignAllLeft metrics:metrics views:views]];

    // Image and button vertical layout - spacing at least 15 between the two
    [headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-padding-[imageView(imageEdge)]->=padding-[button]-padding-|" options:NSLayoutFormatAlignAllLeft | NSLayoutFormatAlignAllRight metrics:metrics views:views]];
}

 Note that in the real app, you wouldn’t be setting the text and image values in this method, but in a separate setter method. I’ve just used examples here to keep the code simpler.

The resulting view looks like this:

The editor thinks this isn’t lurid enough. Enter a longer headline, and without any other changes to the code:

For extra credit, if you call layoutIfNeeded inside an animation block in the setter method for your new headline, the headline view will animate to the new layout!

The last bit of finesse you can add to your VFL strings is the priority of the constraint. To be honest I haven’t had much use for priorities so far so I don’t really want to write too much about them. I think they are probably more useful for OS X. A priority is a float value between 0 and 1000. Apple recommend you use the values contained in the UILayoutPriority enum for readability and consistency. A constraint of priority 1000 (the default) must be satisfied – if you have conflicting constraints of the same priority, you’ll get run time warnings. Lower priority constraints will be set as close to their specified value as possible.

You add priorities to VFL strings using the @ character immediately after the value of the constraint:

  • "|[button(==200@750)]-[label]|" : The button’s width should be 200 points, with a priority of 750.
  • "|-30.0@200-[label]" : The label should be spaced 30 points from the left of the superview, with a priority of 200.

You can add priorities to the metrics dictionary if you want to use the enum values:

NSDictionary *metrics = @{@"lowPriority":@(UILayoutPriorityDefaultLow)};

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-30.0@lowPriority-[label]" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(label)];

 

Summary

We’ve dug further into the possibilities offered by VFL and expanded more on the details given in the official documentation, and there have been some real-life examples used. You can start to get really clever with VFL, building up the strings dynamically (e.g. iterating through an array of subviews) which gives you enormous flexibility.

However, there are still some limitations – you can’t use the multiplier part of constraints, which is great for setting fixed aspect ratios on views, and you can’t use it to link things like the centre of views to the centre of their superviews. To do that, you need to create individual constraints, which is the method I’ll be covering in the next part of this series. I’ll also be sharing some of the category methods I’ve developed to make common layout operations much less verbose.

分享到:
评论

相关推荐

    iOS AutoLayout的代码实现

    在本教程中,我们将专注于使用Visual Format Language (VFL)进行AutoLayout的代码实现。 Visual Format Language是一种用字符串来描述界面布局约束的方法,它可以简洁地表达视图之间的对齐方式、大小和间距。VFL...

    ios-VFL语言实现AutoLayout,屏幕适配.zip

    Visual Format Language (VFL) 是一种简洁的语法,用于以字符串形式描述AutoLayout的约束。在这个"ios-VFL语言实现AutoLayout,屏幕适配.zip"压缩包中,我们很显然会学习如何使用VFL来实现自动布局,从而实现屏幕适配...

    ios使用autolayout布局改变心得1

    - **Visual Format Language (VFL)**:一种用字符串描述约束的语法,可以直观地表示视图间的布局关系。 - **Size Classes**:在iOS8中引入,用于处理不同屏幕方向和设备类型带来的布局差异,提供了更灵活的界面...

    ios autolayout tableview

    使用Visual Format Language (VFL) 或代码可以创建这些约束。 在UITableView中应用AutoLayout,我们需要考虑以下关键点: 1. **Cell的高度**:在AutoLayout中,我们可以通过设置cell的contentView的约束来动态计算...

    IOS6 autolayout例子

    3. **Visual Format Language(VFL)**:一种以字符串形式定义约束的语法,介于IB和代码之间,方便快速创建常见约束。 ### AutoLayout的优化 1. **约束冲突检查**:在应用启动时,AutoLayout会检查并报告约束冲突...

    ios8autoLayout

    总之,"ios8autoLayout"这个项目提供了对iOS8 AutoLayout特性的实战演示,包括Size Classes、Smart Constraints、Visual Format Language和新属性的使用。通过研究这个项目,开发者可以更好地理解和掌握如何在不同...

    iOS页面 Autolayout

    - **Visual Format Language (VFL)**: 是一种基于字符串的语法,用于简洁地创建Autolayout约束。例如,`H:|-10-[label]-10-[button]-|`表示标签和按钮之间的水平间距为10,且它们都与父视图边缘对齐。 - **...

    AutoLayout

    - **Visual Format Language (VFL)**: 一种基于字符串的语法,可以方便地定义视图的布局。例如,`@"H:|-10-[view]-10-|"`表示在水平方向上,view左边距父视图10像素,右边距10像素。 - **AutoLayout Category**: ...

    ios8 vfl以及AutoLayout

    在iOS开发中,AutoLayout和Visual Format Language (VFL)是两种重要的布局工具,用于创建适应不同屏幕尺寸和方向的应用界面。iOS8引入了VFL,使得开发者能够更直观、简洁地定义视图间的约束。下面我们将深入探讨这两...

    ios学习——AutoLayout(1)

    这篇博客“ios学习——AutoLayout(1)”可能介绍了AutoLayout的基础概念和基本用法,但具体内容需要通过博文链接查看。在这里,我们将深入探讨AutoLayout的核心概念和常见用法。 **一、AutoLayout简介** AutoLayout...

    autolayout-helper-ios:UIView助手可轻松为iOS创建常见的自动布局约束

    AutoLayoutHelper UIView助手可轻松为iOS创建常见的自动布局约束问题以编程方式使用“自动布局”可能非常冗长,即为每个规则构建NSLayoutConstraint对象,或者容易出错,例如(使用Visual Format Language字符串)一...

    用代码AutoLayout的Demo

    6. **Visual Format Language (VFL)**:一种用于描述界面布局的文本语法,可以更直观地创建约束。例如:"V:|-10-[label]-10-[button]-|"(表示顶部距父视图10,label和button之间间距10,底部距父视图10)。 7. **...

    AutoLayout Demo

    它提供了Visual Format Language(VFL)和接口构建器(Interface Builder)两种方式来创建约束,开发者可以根据自己的喜好选择合适的方式。 总之,“AutoLayout 等分 Demo”是一个学习和理解AutoLayout基础功能的好...

    iOS8开发~UI布局(三)深入理解autolayout

    5. 使用Visual Format Language(VFL):这是一种用字符串表示约束的方式,可以在代码中更简洁地创建约束。 6. NSLayoutAnchor:自iOS9开始引入的新API,提供了一种更简洁的接口来创建约束,使得代码更易读。 在...

    AutoLayOUt

    在开发过程中,使用Xcode的“Visual Format Language”(VFL)可以帮助理解约束,同时“Debug View Hierarchy”功能可以在运行时可视化界面的约束状态,以便定位布局问题。 七、自适应布局 AutoLayout与...

    ios自动布局 vfl实现

    iOS中的Auto Layout是一种强大的界面布局...总之,Visual Format Language是iOS开发中一种实用的工具,尤其在处理自动布局时。通过熟练掌握VFL,你可以更高效地构建适应各种屏幕尺寸的用户界面,提升应用的用户体验。

    autolayout例子

    AutoLayout是iOS开发中的一个重要概念,它是一种布局管理系统,用于在不同尺寸的屏幕上自动调整UI元素的位置和大小。这个例子中,我们有三个View:两个位于上方,一个位于下方,它们通过AutoLayout进行自动排版。 1...

    Autolayout

    六、使用Visual Format Language (VFL) VFL是一种简洁的语法,用于在代码中创建约束。通过设置`priority`参数,可以方便地在VFL字符串中指定约束的优先级。 七、总结 自动布局通过设置约束优先级为开发者提供了更...

    IOS VFL(可视化语言布局)

    VFL(Visual Format Language)是Autolayout的一种表达方式,通过简单的字符串语法来描述视图间的布局关系。对于初学者来说,理解和掌握VFL可以提升布局效率,减少代码量,同时增加布局的可读性。 **1. VFL的基本...

    iOS Auto Layout开发秘籍 第2版 中文完整版本

    Visual Format Language是一种用于描述界面布局的字符串表示法。通过VFL,开发者可以以人类可读的形式编写约束,如`H:|-10-[view]-10-|`表示view左右与父视图有10个点的距离。 ### 4. Auto Layout优先级和激活状态 ...

Global site tag (gtag.js) - Google Analytics