What to Do When Your VFL Constraints Aren't Respecting Margins in iOS8
Let's say you've got a view with a single button, and you want that button to sit at the top center of the view. You set up the constraints using constraintsWithVisualFormat:
like so:
- (void)layoutSubviews {
[self addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:|-[_button]"
options:0
metrics:nil
views:@{@"_button" : _button}]];
[self addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"H:|-[_button]-|"
options:0
metrics:nil
views:@{@"_button" : _button}]];
[super layoutSubviews];
}
In iOS7 you get exactly what you want:
In iOS8 you end up with a button smooshed underneath the status bar:
What's going on? Apple changed the default behavior of how AutoLayout respects margins in iOS8. So now when you use V:|-[_button]
AutoLayout says "put a default amount of space between the superview and _button
, with no respect to the margins" where previously it would have first taken the margins of the superview into account.
There are a few things you can do about this. First there's a new property of UIView
called preservesSuperviewLayoutMargins
which is set to NO
by default. On paper this seems like just the thing you need, but in testing I couldn't figure out what changing this to YES
actually did. So scrap that one.
Next, you can manually set the edge insets of your superview like so:
- (UIEdgeInsets)layoutMargins {
return UIEdgeInsetsMake(22, 0, 0, 0);
}
This works! But I really hate setting that top margin to a literal value. It seems safe enough now, but the old way seemed pretty safe too until it wasn't.
What we end up doing is setting the constraint to use the topLayoutGuide
property of the view controller instead of referencing the superview directly:
NSDictionary *views = @{ @"_button" : _button,
@"_topLayoutGuide" : _viewController.topLayoutGuide };
[self addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:[_topLayoutGuide][_button]"
options:0
metrics:nil
views:views]];
This works in both iOS7 & iOS8. The only kludgy part is that you need to set a property on your view that references back to the view controller so you can retrieve the topLayoutGuide
, but you can do that easily enough in an initializer.
You can see a complete example project here: https://github.com/richrad/AutoLayoutVFLMargins