Reputation: 2062
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)
Upvotes: 3
Views: 1986
Reputation: 993
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.
Upvotes: 4
Reputation: 2062
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 {};
Upvotes: 1
Reputation: 17043
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
Upvotes: 2