Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Centering view with visual format NSLayoutConstraints

I am trying to center a view using the visual format language.

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView(300)]-|" options:NSLayoutFormatAlignAllCenterY metrics:0 views:views]];

(views is a NSDictionaryOfVariableBindings containing _progressView)

It does not center my view (width: 300) within the self.view (width: 768). It aligns it left with an 8 pixel margin, as if I would have only written @"H:|-[_progressView(300)".

Is the only way to center the view using something like:?

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:_progressView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];

Thanks

like image 249
Joseph Avatar asked Nov 16 '13 16:11

Joseph


3 Answers

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
like image 63
DrummerB Avatar answered Nov 20 '22 12:11

DrummerB


Three approaches:

  1. You can put your constraint adding code in a method or category as suggested by DrummerB.

    You also, effective iOS 9, can use a newer, more concise syntax, using anchors and using activateConstraints:

    [NSLayoutConstraint activateConstraints:@[
        [centerView.centerXAnchor constraintEqualToAnchor:view.centerXAnchor],
        ...
    ]];
    

    Or in Swift 3:

    NSLayoutConstraint.activate([
        centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        ...
    ])
    
  2. You can use UILayoutGuide objects:

    UILayoutGuide *leftGuide = [[UILayoutGuide alloc] init];
    [view addLayoutGuide:leftGuide];
    UILayoutGuide *rightGuide = [[UILayoutGuide alloc] init];
    [view addLayoutGuide:rightGuide];
    

    and define them to be equal to each other with VFL like so:

    H:|[leftGuide][_progressView(==300)][rightGuide(==leftGuide)]|
    

    If you can't use layout guides (e.g. you need to support iOS versions prior to 9.0, you want to create this in IB, etc.) you can use non-visible UIView objects (alpha of zero or background color of clear) in a very similar manner. Just add these "spacer" views to your view hierarchy, use horizontal constraints like above, but simply configure the views so you can't see them.

    This technique would be a cumbersome way of solving the "how do a center a view" problem, but is extremely useful if you, for example, want to evenly space a dozen views and you can't use a UIStackView for some reason. You can use however many UILayoutGuide (or invisible UIView) objects that you want in order to achieve the desired effect.

  3. For the sake of completeness, I should point out that it's possible to achieve the effect by using options of NSLayoutFormatAlignAllCenterX/Y, as outlined in https://stackoverflow.com/a/14917695/1271826, but I confess that I find it unnecessarily confusing.

Personally, I think the first approach makes the most sense for your scenario. The second approach is useful when trying to evenly space a bunch of controls, but is too clumsy for this simple scenario. The third approach is an intellectual curiosity, but I wouldn't suggest it in any real-world application.

like image 43
Rob Avatar answered Nov 20 '22 10:11

Rob


For those who need it, @DrummerB's ObjC category as Swift extension:

extension UIView {

    func centerInSuperview() {
        self.centerHorizontallyInSuperview()
        self.centerVerticallyInSuperview()
    }

    func centerHorizontallyInSuperview(){
        let c: NSLayoutConstraint = NSLayoutConstraint(item: self, attribute: .CenterX, relatedBy: .Equal, toItem: self.superview, attribute: .CenterX, multiplier: 1, constant: 0)
        self.superview?.addConstraint(c)
    }

    func centerVerticallyInSuperview(){
        let c: NSLayoutConstraint = NSLayoutConstraint(item: self, attribute: .CenterY, relatedBy: .Equal, toItem: self.superview, attribute: .CenterY, multiplier: 1, constant: 0)
        self.superview?.addConstraint(c)
    }

}
like image 32
Koen. Avatar answered Nov 20 '22 11:11

Koen.