71
votes

I have a single window with a single custom view in it, and I want the custom view to resize with the window so that it entirely fills it at any time. If I write:

NSView *contentView = [self.window contentView];
CustomView *customView = [[CustomView alloc] initWithFrame:[contentView bounds]];
[contentView addSubview:customView];
[contentView addConstraint:
    [NSLayoutConstraint constraintWithItem:customView
        attribute:NSLayoutAttributeWidth
        relatedBy:NSLayoutRelationEqual
        toItem:contentView
        attribute:NSLayoutAttributeWidth
        multiplier:1
        constant:0]];
[contentView addConstraint:
    [NSLayoutConstraint constraintWithItem:customView
        attribute:NSLayoutAttributeHeight
        relatedBy:NSLayoutRelationEqual
        toItem:contentView
        attribute:NSLayoutAttributeHeight
        multiplier:1
        constant:0]];

Then the window doesn't let me resize it.
If I add:

[customView setTranslatesAutoresizingMaskIntoConstraints:NO];

Then the custom view doesn't appear (drawRect: seems to never be called). I tried different ways (including the visual format @"|[customview]|") but I always run into the same problems. I know it could be done with the older autoresizing system, with:

[customView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];

but I want to use the Cocoa Auto Layout system, and I want to use it for more complicated cases (like several custom views that always fill the window).

Does anyone know what is wrong and how I should use the Auto Layout system to get the result that I want?

4
This might help for debugging: [[contentView constraints].window visualizeConstraints:contentView.constraints];Regexident
I’ve tested three different ways of using Auto Layout for the situation you’ve described and all three work. There must be something else in your code that’s preventing Auto Layout from working as expected. Could you upload a minimal test case that reproduces your problem?user557219
Indeed, it does work, I cannot find what the problem was... I may at one point have applied the constraints before adding the view to the superview (but that does output an error on the console and I haven't seen it). Or maybe there was something else wrong, and by asking the question, it made me rewrite everything from scratch without the initial problem...Guillaume

4 Answers

109
votes

With Auto Layout, there are (at least) three possible ways to constrain a view so that it occupies the entire window’s content view, resizing when appropriate.

Visual format constraints with regard to superview

NSView *contentView = [_window contentView];
MyView *customView = [[MyView alloc] initWithFrame:[contentView bounds]];
[customView setTranslatesAutoresizingMaskIntoConstraints:NO];

[contentView addSubview:customView];

NSDictionary *views = NSDictionaryOfVariableBindings(customView);

[contentView addConstraints:
    [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[customView]|"
        options:0
        metrics:nil
        views:views]];

[contentView addConstraints:
    [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[customView]|"
    options:0
    metrics:nil
    views:views]];

Programmatic constraints for the edges

(this should be equivalent to the visual format above)

+ (void)addEdgeConstraint:(NSLayoutAttribute)edge superview:(NSView *)superview subview:(NSView *)subview {
    [superview addConstraint:[NSLayoutConstraint constraintWithItem:subview
        attribute:edge
        relatedBy:NSLayoutRelationEqual
        toItem:superview
        attribute:edge
        multiplier:1
        constant:0]];
}

and

NSView *contentView = [_window contentView];
MyView *customView = [[MyView alloc] initWithFrame:[contentView bounds]];
[customView setTranslatesAutoresizingMaskIntoConstraints:NO];

[contentView addSubview:customView];

[[self class] addEdgeConstraint:NSLayoutAttributeLeft superview:contentView subview:customView];
[[self class] addEdgeConstraint:NSLayoutAttributeRight superview:contentView subview:customView];
[[self class] addEdgeConstraint:NSLayoutAttributeTop superview:contentView subview:customView];
[[self class] addEdgeConstraint:NSLayoutAttributeBottom superview:contentView subview:customView];

Programmatic constraint for the size

NSView *contentView = [_window contentView];
MyView *customView = [[MyView alloc] initWithFrame:[contentView bounds]];
[customView setTranslatesAutoresizingMaskIntoConstraints:NO];

[contentView addSubview:customView];

[contentView addConstraint:
    [NSLayoutConstraint constraintWithItem:customView
        attribute:NSLayoutAttributeWidth
        relatedBy:NSLayoutRelationEqual
        toItem:contentView
        attribute:NSLayoutAttributeWidth
        multiplier:1
        constant:0]];
[contentView addConstraint:
    [NSLayoutConstraint constraintWithItem:customView
        attribute:NSLayoutAttributeHeight
        relatedBy:NSLayoutRelationEqual
        toItem:contentView
        attribute:NSLayoutAttributeHeight
        multiplier:1
        constant:0]];

The third approach is the one listed in the question and it may not work if there are further constraints. For example, without:

[customView setTranslatesAutoresizingMaskIntoConstraints:NO];

the original autoresize mask is applied as well, which leads to the behaviour described in the question: the window isn’t resized.

As mentioned by Regexident, you can use:

[_window visualizeConstraints:[contentView constraints]];

to debug Auto Layout. It’s worth checking the console output as well.

47
votes

@Bavarious's answer is good, I will just add a few more things.

It's really important to learn to use the built in debugging support! As with much development, it is not realistic to hope that you will always get everything right on the first shot. This was a major concern with auto layout, so we put a lot of effort into debugging. Steps, briefly:

  1. Determine a view that is in the wrong place. Calling the method -[NSView _subtreeDescription] from gdb and/or passing the arguments -NSShowAllViews YES can help identify which view is wrong.
  2. Determine the constraint or constraints that is wrong or missing. -[NSLayoutConstraint constraintsAffectingLayoutForOrientation:] helps give you a smaller set of constraints to work on. -[NSWindow visualizeConstraints:] can help you see what those constraints are and you can see from that which of those isn't something you want to be there. It also will show you if your layout is ambiguous (not enough constraints).
  3. Determine where the wrong constraint came from. The Cocoa Layout template in Instruments is sort of like the Leaks instrument - it'll show you all the events in the life cycle of a constraint, like where it was created, added to a window, modified, etc. So once you know what constraint is the problem, use the search field in Instruments to filter down to just viewing that constraint, and you can see backtraces for all the lifecycle events and figure out where you did something you didn't want.

Usually the kind of question you'd post (my stuff doesn't work!) will not be enough for people to tell what's wrong, which is one of the reason's it's important to use the debugging stuff. See the WWDC 2011 session video (free for all) and docs for more on this.

Buuuuut I can actually tell what went wrong this time. :-) Before you turned off translatesAutoresizingMaskIntoConstraints, you were more constrained than you wanted to be - the width and height of your view were fixed as well, which is why the window couldn't resize. AFTER you turned it off though, you had ambiguous layout, because you hadn't fastened your view onto anything! You had said how big it should be (same as the superview), but not where it was supposed to be.

Ken

Cocoa Frameworks, primary auto layout author

5
votes

You can create constraints with Layout Anchors with very easy to read format:

Swift2 code

func fit(childView: UIView, parentView: UIView) {
    childView.translatesAutoresizingMaskIntoConstraints = false
    childView.topAnchor.constraintEqualToAnchor(parentView.topAnchor).active = true
    childView.leadingAnchor.constraintEqualToAnchor(parentView.leadingAnchor).active = true
    childView.trailingAnchor.constraintEqualToAnchor(parentView.trailingAnchor).active = true
    childView.bottomAnchor.constraintEqualToAnchor(parentView.bottomAnchor).active = true
}

Use:

parrentView.addSubview(childView)
fit(childView, parentView: parrentView)
0
votes

I have discovered that -drawRect: willnot get called in the event the frame rectangle is 0,0,0,0. Undesirable constraints seem to cause the frame to become 0,0,0,0.