How to make subview resize automatically, when subview was created programmatically?

I am beginning to pull out my hair: I am trying to create a subview programmatically that would adjust its size automatically when the user resizes the window. More details below.

I have searched the documentation (e.g., Apple "Auto Layout Guide"), searched the internet (and tried to follow a lot of examples on stackexchange), all to no avail.


More specifically, I have a very simple setup that consists of a MasterViewController that has its own XIB.

It's declared like this:


@interface MasterViewController : NSViewController
{
    ArtSaverView * saver_;

(there is not much more to it)


It creates a subview programmatically that is an instance of my own class ArtSaverView, which is a subclass of ScreenSaverView, which is a subblass of NSView.

So, my MasterViewController has this method:

- (void) viewDidLoad
{
    [super viewDidLoad];
    saver_ = [[ArtSaverView alloc] initWithFrame: self.view.frame isPreview: NO ];
    [self.view addSubview: saver_];


Now, when the user resizes the window, the view of the ArtSaverView instance does not resize, no matter what I do.

For instance, I have tried this later in the method viewDidLoad:


    [saver_ setTranslatesAutoresizingMaskIntoConstraints: NO];      
    NSDictionary * views = NSDictionaryOfVariableBindings( saver_ );
    NSArray * horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[saver_]-0-|"
                                                                            options: 0 metrics: nil views: views];
    NSArray * verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[saver_]-0-|"
                                                                            options: 0 metrics: nil views: views];
    [self.view addConstraints: horizontalConstraints ];
    [self.view addConstraints: verticalConstraints ];
    [NSLayoutConstraint activateConstraints: horizontalConstraints ]; 
    [NSLayoutConstraint activateConstraints: verticalConstraints ];


But nothing changes.


Is there anything else I can try?


All kinds of hints, insights, pointers will be highly appreciated.

Accepted Reply

In windowDidResize, you should put the code to:

- change constraints constants if needed (don't create new constraints each time) and call

        self.window?.layoutIfNeeded()

- or reset directly the views frames.


First is preferable.


For this,

  • your constraints should be declared as class properties.
  • They should be initiated in viewDidLoad
  • And modified if needed in windowDidResize:
     someConstraint.const = newValue

Replies

This is working for me...

#import "ArtSaverView.h"


@implementation ArtSaverView


- (nullable instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview

{

if ((self = [super initWithFrame:frame isPreview:isPreview]) != nil)

{

NSLog(@"autoresizesSubviews - %d", self.autoresizesSubviews);

NSLog(@"autoresizingMask - %d", (int)self.autoresizingMask);


self.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;

self.translatesAutoresizingMaskIntoConstraints = YES;

}


return self;

}


#if 0

- (void)resizeSubviewsWithOldSize:(NSSize)oldSize

{

NSLog(@"resizeSubviewsWithOldSize");

}

- (void)resizeWithOldSuperviewSize:(NSSize)oldSize

{

NSLog(@"resizeWithOldSuperviewSize");

}

#endif // 0

@end


without self.autoresizingMask set my resize* messages weren't called.
when the resize* messages are compiled in they are called instead of auto-resizing.
Also, for the window containing view I set AutoResizing on the super view to be both to the containing window and to the contained objects so that the red box inside the demo area resizes WITH the window.

You do it in viewDidload, but the view is already loaded. So it is not called (put a log in the windowDidLoad and you will see).


You should put the resize in another func, such as in

    func windowDidResize(_ notification: Notification) { }


In objc:

- (void)windowDidResize:(NSNotification *)notification;

Thanks a lot. But I still doesn't work for me.


I tried your suggestion, so I added the lines with autoresizingMask and translatesAutoresizingMaskIntoConstraints to ArtSaverView.


And I have implemented these methods in the ArtSaverView:

- (void) setFrameSize: (NSSize) newSize
- (void) viewDidEndLiveResize
- (void) resizeSubviewsWithOldSize: (NSSize) oldSize
- (void) resizeWithOldSuperviewSize: (NSSize) oldSize


But i have two puzzles:

1.

No matter what I do, only viewDidEndLiveResize gets called duering runtime when I resize the window.

(and resizeWithOldSuperviewSize only gets called once at the beginning.)


2.

Even in viewDidEndLiveResize, I seem to be unable to resize the ArtSaverView; the rectangle always keeps its initial size.

Here is how I implemented it:

- (void) viewDidEndLiveResize
{
//    drawRect_ = [self frame];
//    drawRect_ = [super bounds];
    drawRect_ = [selfbounds];
 
    [self logMessage: [NSString stringWithFormat: @"viewDidEndLiveResize: origin = %f , %f ;  size = %f , %f",
                       drawRect_.origin.x, drawRect_.origin.y, drawRect_.size.width, drawRect_.size.height] asError: NO];
  
    [self setNeedsDisplay: YES];
    [super viewDidEndLiveResize];
}

This does get called whenever I resize the window, but the size of the ArtSaverView's rectangle never changes.


What am I missing?


Do have to implement anything else in MasterViewController?

Thanks a lot for your response.


Could you please give a few more details?

What should I implement in windowDidResize ? (obj-c)

In windowDidResize, you should put the code to:

- change constraints constants if needed (don't create new constraints each time) and call

        self.window?.layoutIfNeeded()

- or reset directly the views frames.


First is preferable.


For this,

  • your constraints should be declared as class properties.
  • They should be initiated in viewDidLoad
  • And modified if needed in windowDidResize:
     someConstraint.const = newValue

Thanks a million, that helped me move forward. It almost works now.


First of all, I tried the first method (layoutIfNeeded in windowDidResize, constraints are declared as class properties and created in viewDidLoad). That did not change anything.


Then I tried this:


- (void) windowDidEndLiveResize: (NSNotification *) notification
{
    logMessage( log_client_, @"windowDidEndLiveResize", NO );  //TODO: RAUS  
//    [self.window layoutIfNeeded];  // doesn't make the view's resize
    self.masterViewController.view.frame = ((NSView*) self.window.contentView).bounds;
}


This seems to work. (Is this a correct way to do it?)


Now, there is still one bug. In ArtSaverView's -initWithFrame:, I create two layers, one for images, one for 1-2 lines of text. They are created like this:

    mainLayer_ = [CALayer layer];
    mainLayer_.zPosition = 0.0;
    mainLayer_.delegate = self;
    [mainLayer_ setNeedsDisplay];                                            // causes the layer content to be drawn in -drawRect:
    [self setLayer: mainLayer_];
    self.wantsLayer = YES;
    [self setNeedsDisplay: YES];
   
    // the image layer 
    currentLayer_ = [CALayer layer];
    [mainLayer_ addSublayer: currentLayer_];
   
    textLayer_ = [CATextLayer layer];
    textLayer_.zPosition = 0.0;
    textLayer_.bounds = NSRectToCGRect( drawRect_ );
    textLayer_.anchorPoint = CGPointMake( 0, 0 );
// ... omitting some less important settings here
    textLayer_.position = CGPointMake( 7.0, 7.0 );       // offset from bottom left corner
    textLayer_.shadowColor = CGColorCreateGenericRGB( 0.0, 0.0, 0.0, 1.0 );
    textLayer_.shadowOpacity = 0.5;
    textLayer_.shadowRadius = 4.0;


The funny thing is, after resizing the window, the text layer springs to the top left, but its shadow remains at the bottom left, like this:

https://owncloud.informatik.uni-bremen.de/index.php/s/wqPEEdDbWFGr47a

(never mind the black part of the window). The proper layout looks like this:

https://owncloud.informatik.uni-bremen.de/index.php/s/wqPEEdDbWFGr47a

(never mind it's a different image).


Any ideas what might cause this bug?


Thanks a lot in advance.