Question

So I have a view that I set in IB and I need to change the frame programmatically. For some reason, the frame keeps reverting back to its IB location after I've set it. I subclassed NSView and logged the frame in the -setFrame:(NSRect)frameRect method and it looks like -setFrame: is getting called twice -- once when I set it (where it logs the new values) and once when it reverts (where it logs the IB values). I can't seem to distill the root of the problem because in some situations (such as if I have an NSButton dedicated to setting it or have a timer setting the frame) it works perfectly, but if I have the -setFrame: call in-line with my other code, it always reverts.

Edit:

This is a simple example that shows the problem (the original frame in the IB is {{20, 118}, {48, 48}}):

AppDelegate.m:

#import "AppDelegate.h"

@implementation AppDelegate

- (void)awakeFromNib{
    [self.button setFrame:NSMakeRect(50, 10, 100, 100)];
}

@end

Log:

2014-02-18 18:01:40.206 WHS-ChangingFrameTest[15210:303] Frame: {{50, 10}, {100, 100}}
2014-02-18 18:01:41.223 WHS-ChangingFrameTest[15210:303] Frame: {{20, 118}, {48, 48}}

Edit #2:

Call Stack from when I edit the frame (from original app):

0   MyApp                 0x000000010000203c -[FrameLogProgressIndicator setFrame:] + 284
1   MyApp                 0x000000010001c994 -[SubjectViewController updateTableViewHeight] + 1284
2   MyApp                 0x000000010001c468 -[SubjectViewController updateUI] + 4664
3   MyApp                 0x0000000100012f2f -[TabMenuViewController updateDisplayingBlock:] + 975
4   MyApp                 0x0000000100010c59 -[TabMenuViewController switchBlockFromDaySchedulePopover:] + 873
5   AppKit                              0x00007fff82eea959 -[NSApplication sendAction:to:from:] + 342
6   AppKit                              0x00007fff82eea7b7 -[NSControl sendAction:to:] + 85
7   AppKit                              0x00007fff82eea6eb -[NSCell _sendActionFrom:] + 138
8   AppKit                              0x00007fff82ee8bd3 -[NSCell trackMouse:inRect:ofView:untilMouseUp:] + 1855
9   AppKit                              0x00007fff82ee8421 -[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:] + 504
10  AppKit                              0x00007fff82ee7b9c -[NSControl mouseDown:] + 820
11  AppKit                              0x00007fff82edf50e -[NSWindow sendEvent:] + 6853
12  AppKit                              0x00007fff82edb644 -[NSApplication sendEvent:] + 5761
13  AppKit                              0x00007fff82df121a -[NSApplication run] + 636
14  AppKit                              0x00007fff82d95bd6 NSApplicationMain + 869
15  MyApp                 0x00000001000020a2 main + 34
16  libdyld.dylib                       0x00007fff8152a7e1 start + 0
17  ???                                 0x0000000000000003 0x0 + 3
)

Call Stack from when frame is reverting back:

0   MyApp                 0x000000010000203c -[FrameLogProgressIndicator setFrame:] + 284
1   AppKit                              0x00007fff82e21e77 -[NSView resizeWithOldSuperviewSize:] + 659
2   AppKit                              0x00007fff82e21307 -[NSView resizeSubviewsWithOldSize:] + 318
3   AppKit                              0x00007fff82f08399 NSViewLevelLayout + 44
4   AppKit                              0x00007fff82f07e65 -[NSView _layoutSubtreeHeedingRecursionGuard:] + 112
5   CoreFoundation                      0x00007fff84b524a6 __NSArrayEnumerate + 582
6   AppKit                              0x00007fff82f07fc6 -[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
7   CoreFoundation                      0x00007fff84b524a6 __NSArrayEnumerate + 582
8   AppKit                              0x00007fff82f07fc6 -[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
9   CoreFoundation                      0x00007fff84b524a6 __NSArrayEnumerate + 582
10  AppKit                              0x00007fff82f07fc6 -[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
11  CoreFoundation                      0x00007fff84b524a6 __NSArrayEnumerate + 582
12  AppKit                              0x00007fff82f07fc6 -[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
13  AppKit                              0x00007fff82f07cfe -[NSView layoutSubtreeIfNeeded] + 615
14  AppKit                              0x00007fff82f034ac -[NSWindow(NSConstraintBasedLayout) layoutIfNeeded] + 201
15  AppKit                              0x00007fff82dfd0a8 _handleWindowNeedsDisplayOrLayoutOrUpdateConstraints + 446
16  AppKit                              0x00007fff833c8901 __83-[NSWindow _postWindowNeedsDisplayOrLayoutOrUpdateConstraintsUnlessPostingDisabled]_block_invoke_01208 + 46
17  CoreFoundation                      0x00007fff84b20417 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
18  CoreFoundation                      0x00007fff84b20381 __CFRunLoopDoObservers + 369
19  CoreFoundation                      0x00007fff84afb7b8 __CFRunLoopRun + 728
20  CoreFoundation                      0x00007fff84afb0e2 CFRunLoopRunSpecific + 290
21  HIToolbox                           0x00007fff8231aeb4 RunCurrentEventLoopInMode + 209
22  HIToolbox                           0x00007fff8231ab94 ReceiveNextEventCommon + 166
23  HIToolbox                           0x00007fff8231aae3 BlockUntilNextEventMatchingListInMode + 62
24  AppKit                              0x00007fff82dfa533 _DPSNextEvent + 685
25  AppKit                              0x00007fff82df9df2 -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 128
26  AppKit                              0x00007fff82df11a3 -[NSApplication run] + 517
27  AppKit                              0x00007fff82d95bd6 NSApplicationMain + 869
28  MyApp                 0x00000001000020a2 main + 34
29  libdyld.dylib                       0x00007fff8152a7e1 start + 0
30  ???                                 0x0000000000000003 0x0 + 3
)

Let me know if I need to post the code from the methods used in the stack for this to be useful. (sorry, I've never really dealt with this stuff before)

Was it helpful?

Solution 3

So I managed to stop the resizing of the views by subclassing them and overriding -resizeWithOldSuperviewSize: to just do nothing, like this:

- (void)resizeWithOldSuperviewSize:(NSSize)oldSize {};

OTHER TIPS

An alternative to replacing the content of resizeWithOldSuperviewSize: is to inform the auto layout system that you don't want your NSView to be resized. This will have the effect of keeping your NSView at the origin that you specified programmatically thereby keeping your override of the interface builder intact. You do this by:

[<id> setAutoresizingMask:NSViewNotSizable];
[<id> setTranslatesAutoresizingMaskIntoConstraints:YES];

where <id> would be the instance of your NSView, i.e. self.button. The first line states that the view is not sizable, while the second states that the mask should be considered a constraint by the auto layout system. Your revised AppDelegate.m would then be:

#import "AppDelegate.h"

@implementation AppDelegate

- (void)awakeFromNib{
    [self.button setAutoresizingMask:NSViewNotSizable];
    [self.button setTranslatesAutoresizingMaskIntoConstraints:YES];
    [self.button setFrame:NSMakeRect(50, 10, 100, 100)];
}

@end

Update: If you use this method with an NSView that you plan to set hidden, the auto layout system will still take the frame of the hidden NSView into account when the frame's superview/window is resized. This means that if the hidden NSView would be outside the visible area of the superview after resize, the auto layout system will prevent the superview from resizing properly and instead will force the superview frame to enclose the hidden NSView.

One naïve solution to this problem is to set the width and height of the NSView to zero after setting hidden:YES and restoring the width and height before setting hidden:NO. For example, at some point in your code using your NSView self.button:

...
[self.button setHidden:YES];
[self.button setFrameSize:NSZeroSize];
...

and later:

...
[self.button setFrameSize:NSMakeSize(160, 90)];
[self.button setHidden:NO];
...

However, if you have width/height auto layout constraints set on the NSView (either programmatically or through Interface Builder), these changes may throw warnings like:

Unable to simultaneously satisfy constraints:
(
    "<NSLayoutConstraint:0x608000082990 H:[NSButton:0x6080001200a0(100)]>",
    "<NSAutoresizingMaskLayoutConstraint:0x60800008bae0 h=--& v=--& H:[NSButton:0x6080001200a0(0)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x608000082990 H:[NSButton:0x6080001200a0(100)]>

You can either ignore these warnings, reduce the constraints priority to a low number, change the constraint from = to , or alternately you can simply setTranslatesAutoresizingMaskIntoConstraints:NO before hiding the NSView, and setting it to YES prior to unhiding the NSView:

...
[self.button setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.button setHidden:YES];
...

And when we unhide the NSView:

...
[self.button setTranslatesAutoresizingMaskIntoConstraints:YES];
[self.button setHidden:NO];
...

You can also automate this by subclassing the NSView and overriding setHidden: (note the !):

- (void)setHidden:(BOOL)hidden {
    [self setTranslatesAutoresizingMaskIntoConstraints:!hidden];
    [super setHidden:hidden];
}

You can then simply call [self.button setHidden:YES]; or [self.button setHidden:NO]; and the overridden method will take care of everything.

From awakeFromNib description:

Because the order in which objects are instantiated from an archive is not guaranteed, your initialization methods should not send messages to other objects in the hierarchy. Messages to other objects can be sent safely from within an awakeFromNib method. Typically, you implement awakeFromNib for objects that require additional set up that cannot be done at design time. For example, you might use this method to customize the default configuration of any controls to match user preferences or the values in other controls. You might also use it to restore individual controls to some previous state of your application.

I am not 100% sure but I am strongly suggest to move

[self.button setFrame:NSMakeRect(50, 10, 100, 100)];

to

- (void)viewDidLoad

method. Also you should not forget to call super's methods - in some cases it could be critical. So your final code should look like this:

@implementation AppDelegate

- (void)awakeFromNib{
    [super awakeFromNib];
    ... non GUI initialization
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.button setFrame:NSMakeRect(50, 10, 100, 100)];
}

@end

UPDATE: Thanks for call stuck. It seems that your view is autoresized. You should check autoresizingMask and auto-layout constraints

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top