Home > Blockchain >  programmatic NSLayoutConstraint in Mac app resizes window contentView
programmatic NSLayoutConstraint in Mac app resizes window contentView

Time:11-07

I'm trying to get auto layout working programmatically in an Objective-C Mac app.

The goal pretty basic, a simple toolbar-like view across the top of the window. Height should not change, view should stay at top of window, and width should resize with the window.

When I configure this in the .xib it works great. Without worrying about height, I just add leading, trailing, and top constraint connections from the custom view to the superview (which is the NSWindow's contentView). I don't have to change any priorities or other settings.

I need to do this programmatically, creating window, subview, and constraints in code. I've tried using both anchors and NSLayoutConstraint objects (both parameterized and based on visual layout string). In all cases I get a bizarre behavior where the window's contentView resizes, down to height zero, and nothing is drawn to the screen.

Here's one complete approach, using anchors:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {

// make window
NSRect windowRect = NSMakeRect(0.0, 0.0, 900.0, 200.0);
NSWindowStyleMask windowStyle = NSWindowStyleMaskTitled
    | NSWindowStyleMaskClosable
    | NSWindowStyleMaskResizable
    | NSWindowStyleMaskMiniaturizable;

NSWindow * win = [[NSWindow alloc] initWithContentRect:windowRect
                                                    styleMask:windowStyle
                                                      backing:NSBackingStoreBuffered
                                                        defer:NO];
win.title = @"Window Made in Code";
win.contentView.translatesAutoresizingMaskIntoConstraints = NO;

// add view
NSRect viewFrame = windowRect;
viewFrame.size.height = 80.0;
viewFrame.origin.y = 120.0;
NSView * view = [[MyView alloc] initWithFrame:viewFrame];
[win.contentView addSubview:view];
[view setNeedsDisplay:YES];

// add constraints
[view.leadingAnchor constraintEqualToAnchor:win.contentView.leadingAnchor].active = YES;
[view.trailingAnchor constraintEqualToAnchor:win.contentView.trailingAnchor].active = YES;
[view.topAnchor constraintEqualToAnchor:win.contentView.topAnchor].active = YES;
// note, the above also tried using layoutMargins of superview, same behavior

// show window
[win setIsVisible:YES];
[win center];
self.codeWindow = win;
[self.codeWindow makeKeyAndOrderFront:NSApp]; 

}

If I don't add the constraints, everything draws correctly, but as expected the subview does not resize with the window.

When I add the constraints, the superview (window's contentView) gets resized to height 0, so nothing gets drawn. If I omit the top constraint this does not happen, however the horizontal constraints then work backward: rather than resizing subview as the window is resized, they prevent the Window contentView from resizing horizontally; I can still drag the window larger but debugging see that the content view frame width does not increase.

Here's an alternate code approach I also tried using NSLayoutConstraint factory methods; here I'm setting priority but no different priority corrects the behavior:

NSLayoutConstraint * leadingC = [NSLayoutConstraint constraintWithItem:view
                                                             attribute:NSLayoutAttributeLeading
                                                             relatedBy:NSLayoutRelationEqual
                                                                toItem:view.superview
                                                             attribute:NSLayoutAttributeLeading
                                                            multiplier:1.0
                                                              constant:0.0];
leadingC.priority = NSLayoutPriorityDragThatCannotResizeWindow;
NSLayoutConstraint * trailingC = [NSLayoutConstraint constraintWithItem:view
                                                             attribute:NSLayoutAttributeLeading
                                                             relatedBy:NSLayoutRelationEqual
                                                                toItem:view.superview
                                                             attribute:NSLayoutAttributeLeading
                                                            multiplier:1.0
                                                              constant:0.0];
trailingC.priority = NSLayoutPriorityDragThatCannotResizeWindow;
NSLayoutConstraint * topC = [NSLayoutConstraint constraintWithItem:view
                                                             attribute:NSLayoutAttributeTop
                                                             relatedBy:NSLayoutRelationEqual
                                                                toItem:view.superview
                                                             attribute:NSLayoutAttributeTop
                                                            multiplier:1.0
                                                              constant:0.0];
[NSLayoutConstraint activateConstraints:@[leadingC, trailingC, topC]];                                                           
[view.superview addConstraints:self.constraints];

Since I'm getting the same bad result trying this several different ways I feel like I must be completely missing something with NSLayoutConstraint, but I haven't been able to figure it out from tutorials or documentation. I did compare via debugging layout constraints at runtime between .xib and code-created windows but haven't been able to reconstruct what I'm doing wrong from the differences, except that the .xib superview has NSAutoresizingMaskLayoutConstraint objects.

If I comment-out win.contentView.translatesAutoresizingMaskIntoConstraints = NO; I do get resizing behavior programmatically, but that is confusing to me - I thought when we created explicit constraints we were supposed to set that flag to NO?

Thanks for any suggestions on how to get this working!

CodePudding user response:

When you opt into constraints, you have to completely describe the size of the view with the constraints. In this case, you've specified the width of your toolbar, and you set it's position, but it looks like you are trying to fix the height with the frame you initially give the view. You also have to have a constraint to set its height.

Also translatesAutoresizingMaskIntoConstraints describes how a view relates to its superview, not its child views. So you should be setting that on the toolbar view, not on the contentView of the window. Try this code:

#import "AppDelegate.h"

@interface AppDelegate ()
@end

@interface MyView : NSView
@end
@implementation MyView

- (void)drawRect:(NSRect)dirtyRect
{
    CGContextRef cgContext = [[NSGraphicsContext currentContext] CGContext];

    CGContextSetFillColorWithColor(cgContext, [[NSColor yellowColor] CGColor]);
    CGContextFillRect(cgContext, self.bounds);
}

@end

@implementation AppDelegate
{
    NSWindowController *disjointController;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    NSRect windowRect = NSMakeRect(0.0, 0.0, 900.0, 200.0);
    NSWindowStyleMask windowStyle = NSWindowStyleMaskTitled
    | NSWindowStyleMaskClosable
    | NSWindowStyleMaskResizable
    | NSWindowStyleMaskMiniaturizable;

    NSWindow * win = [[NSWindow alloc] initWithContentRect:windowRect
                                                 styleMask:windowStyle
                                                   backing:NSBackingStoreBuffered
                                                     defer:NO];
    win.title = @"Window Made in Code";

    // add view
    NSView *view = [[MyView alloc] initWithFrame: windowRect];
    view.translatesAutoresizingMaskIntoConstraints = NO;
    view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
    [win.contentView addSubview: view];

    // add constraints
    [view.leadingAnchor constraintEqualToAnchor: win.contentView.leadingAnchor].active = YES;
    [view.trailingAnchor constraintEqualToAnchor: win.contentView.trailingAnchor].active = YES;
    [view.topAnchor constraintEqualToAnchor: win.contentView.topAnchor].active = YES;
    [view.heightAnchor constraintEqualToConstant: 80].active = YES;

    // show window
    [win center];

    disjointController = [[NSWindowController alloc] initWithWindow: win];

    [win makeKeyAndOrderFront:NSApp];
}


- (void)applicationWillTerminate:(NSNotification *)aNotification {
    // Insert code here to tear down your application
}


- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
    return YES;
}
@end
  • Related