Centering a View in Its Superview Using Visual Format Language

Centering a view in its superview using Visual Format Language

Currently, no, it doesn't look like it is possible to center a view in the superview using only VFL. It is, however, not that difficult to do it using a single VFL string and a single extra constraint (per axis):

VFL: "|-(>=20)-[view]-(>=20)-|"

[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:view.superview
attribute:NSLayoutAttributeCenterX
multiplier:1.f constant:0.f];

One would think that you would simply be able to do this (which is what I initially thought and tried when I saw this question):

[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=20)-[view(==200)]-(>=20)-|"
options: NSLayoutFormatAlignAllCenterX | NSLayoutFormatAlignAllCenterY
metrics:nil
views:@{@"view" : view}];

I tried many different variations of the above trying to bend it to my will, but this does not appear to apply to the superview even when explicitly having two separate VFL strings for both axes (H:|V:). I then started to try and isolate exactly when the options do get applied to the VFL. They appear to not apply to the superview in the VFL and will only apply to any explicit views that are mentioned in the VFL string (which is disappointing in certain cases).

I hope in the future Apple adds some kind of new option to have the VFL options take into account the superview, even if doing it only when there is only a single explicit view besides the superview in the VFL. Another solution could be another option passed into the VFL that says something like: NSLayoutFormatOptionIncludeSuperview.

Needless to say, I learned a lot about VFL trying to answer this question.

Centering Horizontal in visual format language

If you want to make it center horizontally/vertically both as i understand from the image you can make it like this:

// Center horizontally
var constraints = NSLayoutConstraint.constraintsWithVisualFormat(
"V:[superview]-(<=1)-[label2]",
options: NSLayoutFormatOptions.AlignAllCenterX,
metrics: nil,
views: ["superview":view,"label2":label2])

view.addConstraints(constraints)

// Center vertically
constraints = NSLayoutConstraint.constraintsWithVisualFormat(
"H:[superview]-(<=1)-[label1][label2][label3]",
options: NSLayoutFormatOptions.AlignAllCenterY,
metrics: nil,
views: ["superview":view, "label1":label1,"label2":label2,"label3":label3])

view.addConstraints(constraints)

so it will be look like this:
Sample Image

Centering view with visual format NSLayoutConstraints

This format string

@"H:|-[_progressView(300)]-|"

doesn't tell AutoLayout to center progressView. Instead it says that progressView should be 300 wide and have a system defined standard margin on either side to the superview's edges. Obviously this can't be satisfied, so Auto Layout drops some of the constraints (you probably get some logs on the console). The constraint that is dropped in your case is probably the margin on the right.

To really center a view in it's superview you have to use the verbose constraint method instead of the visual string format, as you already figured out. However, you can easily put that into a nice category like this:

@interface UIView (MyLayout)
- (void)centerHorizontallyInSuperview;
@end

@implementation UIView (MyLayout)
- (void)centerHorizontallyInSuperview {
NSLayoutConstraint *c;
c = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.superview
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0];
[self.superview addConstraint:c];
}
@end

How to center two views in super view with greater than or equal to constraints

It's very difficult to center elements using VFL.

It's also difficult to center two elements unless they are embedded in a UIView or a UIStackView.

Here is one option by embedding the labels in a "container" UIView:

class MyVC: UIViewController {
lazy var titleLabel: UILabel = {
let l = UILabel(frame: .zero)
l.translatesAutoresizingMaskIntoConstraints = false
l.text = "Hello World"
l.font = .systemFont(ofSize: 50)
l.textColor = .black

// center the text in the label - change to .left if desired
l.textAlignment = .center

return l
}()

lazy var descLabel: UILabel = {
let l = UILabel(frame: .zero)
l.translatesAutoresizingMaskIntoConstraints = false
l.text = "description"
l.font = .systemFont(ofSize: 35)
l.textColor = .gray

// center the text in the label - change to .left if desired
l.textAlignment = .center

return l
}()

lazy var containerView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()

override func viewDidLoad() {
super.viewDidLoad()

view.backgroundColor = .yellow

// give the labels and containerView background colors to make it easy to see the layout
titleLabel.backgroundColor = .green
descLabel.backgroundColor = .cyan
containerView.backgroundColor = .blue

// add containerView to view
view.addSubview(containerView)

// add labels to containerView
containerView.addSubview(titleLabel)
containerView.addSubview(descLabel)

NSLayoutConstraint.activate([

// constrain titleLabel Top to containerView Top
titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor),

// constrain titleLabel Leading and Trailing to containerView Leading and Trailing
titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
titleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),

// constrain descLabel Leading and Trailing to containerView Leading and Trailing
descLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
descLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),

// constrain descLabel Bottom to containerView Bottom
descLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),

// constrain descLabel Top 10-pts from titleLabel Bottom
descLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10.0),

// constrain containerView centered horizontally and vertically
containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor),

])

}

}

Result:

Sample Image

Is there a way to use Visual Format Language to constrain a view to the bottom of a navigation bar?

You can set the top constraint to UIViewController.view if you are setting edgesForExtendedLayout = [], this will keep the view of your UIViewController just below of your navigation bar

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.edgesForExtendedLayout = []
let view = UIView(frame: .zero)
view.backgroundColor = UIColor.red
view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view)

self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[view]-0-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: ["view":view]));
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[view(50)]", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: ["view":view]));
}

Result

Sample Image

Different between | and [superview] in Visual Format Language?

The original code uses a trick (or a hack).

AlignAllCenterY was not meant to center views within the container. The options are there to specify the relative position of the subviews - for example, if you have 3 labels in the same container, you can make them all top aligned or center aligned between themselves - not with the container (implicitly specified by |).

The trick is that when you specify the superview explicitly, the framework doesn't realize it a adds the constraints.

The correct way to center a view in its container is the following:

let centerX = NSLayoutConstraint(item: label, 
attribute: .CenterX,
relatedBy: .Equal,
toItem: view,
attribute: .CenterX,
multiplier: 1.0,
constant: 0.0);
view.addConstraint(centerX);

let centerY = NSLayoutConstraint(item: label,
attribute: .centerY,
relatedBy: .Equal,
toItem: view,
attribute: .centerY,
multiplier: 1.0,
constant: 0.0);
view.addConstraint(centerY);

How to use Visual Format Language to set constraints in Swift?

Visual Format Language allows you to apply programmatic constraints using visual syntax strings. As per the Apples Documentation, the idea is the text visually matches the layout.

Let's break down the syntax part for better understanding:

H: (Horizontal) //horizontal direction
V: (Vertical) //vertical direction
| (pipe) //superview
- (dash) //standard spacing (generally 8 points)
[] (brackets) //name of the object (uilabel, unbutton, uiview, etc.)
() (parentheses) //size of the object
== equal widths //can be omitted
-16- non standard spacing (16 points)
<= less than or equal to
>= greater than or equal to
@250 priority of the constraint //can have any value between 0 and 1000

Now, in order to apply constraint to a view using visual format language, fir we need to make translatesAutoresizingMaskIntoConstraints false for the view, on which we are going to apply constraints:

imageView.translatesAutoresizingMaskIntoConstraints = false

then we need to prepare a dictionary for all the views, which are to be used in VFL like:

let viewDictionary = NSDictionaryOfVariableBindings(imageView)

then make horizontal and vertical constraints using Visual Format String as per the rules explained above:

let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-[imageView]-|", options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: viewsDictionary)
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|-[imageView(100)]-|", options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: viewsDictionary)

Now, add these constants to your superview like:

view.addConstraints(horizontalConstraints)
view.addConstarints(verticalConstraints)

PS: If you want to make view's width/height dynamic, you need to create a matrics dictionary, pass it in metrics: instead of setting it nil and then using the appropriate keyname for the value. For example:

let metricDict = ["viewHeight":300]
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|-[imageView(viewHeight)]-|", options: NSLayoutConstraint.FormatOptions(), metrics: metricDict, views: viewsDictionary)

Centre subview that is larger than its superview with Visual Format Language

The alignment constraints, like NSLayoutFormatAlignAllCenterX are for aligning multiple sibling objects with respect to each other, not for aligning a single object with respect to its superview. To do that, you need to use…

NSLayoutConstraint *c = [NSLayoutConstraint constraintWithItem:self.outerView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.innerView
attribute:NSLayoutAttributeCenterX
multiplier:1.f constant:0.f];


Related Topics



Leave a reply



Submit