以编程方式设置后返回NSView框架

时间:2023-01-24 08:40:32

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.

我在IB中设置了一个视图我需要以编程方式改变框架。出于某种原因,在我设置了它之后,框架会继续返回到它的IB位置。我将NSView子类化并在-setFrame中记录了框架:(NSRect)frameRect方法,它看起来像-setFrame:在我设置它(它记录新值的地方)和它返回(它记录IB值的地方)的时候被调用两次。我似乎无法提取问题的根源,因为在某些情况下(例如,如果我有一个专门用于设置它的NSButton,或者有一个用于设置框架的计时器),它可以完美地工作,但是如果我有-setFrame:与其他代码一起调用,它总是会恢复。

Edit:

编辑:

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

这是一个简单的例子,说明了问题(IB的原始框架是{{20,118},{48,48}}):

AppDelegate.m:

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:

编辑# 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)

如果需要从堆栈中使用的方法中发布代码,请让我知道这样做是否有用。(不好意思,我以前从来没有处理过这个问题)

3 个解决方案

#1


3  

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:

用oldsuperviewsize替换resizeview内容的另一种方法是:通知自动布局系统您不希望NSView被调整大小。这将使NSView保持在您以编程方式指定的起点,从而保持对接口构建器的覆盖不变。你这样做:

[<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:

其中 将是NSView的实例,即self.button。第一行声明该视图不具有大小,而第二行声明该掩码应该被自动布局系统视为约束。你的修订AppDelegate。m将:

#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.

更新:如果您使用这个方法来设置隐藏的NSView,那么当帧的父视图/窗口被调整大小时,自动布局系统仍然会考虑隐藏的NSView的框架。这意味着,如果隐藏的NSView在调整大小后将位于超视图的可见区域之外,那么自动布局系统将阻止超视图正确调整大小,而将强制超视图框架将隐藏的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:

对于这个问题,一个简单的解决方案是在设置hidden后将NSView的宽度和高度设置为0:YES,并在设置hidden之前恢复宽度和高度:NO。例如,在代码中的某个点使用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:

但是,如果在NSView中设置了宽度/高度自动布局约束(通过编程或通过接口构建器),这些更改可能会抛出以下警告:

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:

你可以忽略这些警告,减少约束优先级较低的数量,改变约束从=≤,或交替只需setTranslatesAutoresizingMaskIntoConstraints:没有隐藏NSView之前,并设置它是的调NSView之前:

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

And when we unhide the NSView:

当我们打开NSView时

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

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

您还可以通过子类化NSView和重写setHidden来实现自动化:(注意!)

- (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.

然后你可以简单地调用[self]。按钮setHidden:是的);或自我。按钮setHidden:没有);重写的方法会处理所有的事情。

#2


2  

From awakeFromNib description:

从awakeFromNib描述:

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.

因为不能保证从归档实例化对象的顺序,所以初始化方法不应该向层次结构中的其他对象发送消息。可以从awakeFromNib方法中安全地发送到其他对象的消息。通常,对于需要额外设置但在设计时无法完成的对象,您可以实现awakeFromNib。例如,您可以使用此方法定制任何控件的默认配置,以匹配用户首选项或其他控件中的值。您还可以使用它将各个控件恢复到应用程序的某些先前状态。

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:

方法。此外,您不应该忘记调用super的方法——在某些情况下,它可能非常重要。所以你的最终代码应该是这样的:

@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

更新:谢谢来电。看来你的观点是自动调整的。您应该检查autoresizingMask和自动布局约束

#3


1  

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

因此,我通过对视图进行子类化和重写-resizeWithOldSuperviewSize来阻止视图的大小调整:什么都不做,如下所示:

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

#1


3  

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:

用oldsuperviewsize替换resizeview内容的另一种方法是:通知自动布局系统您不希望NSView被调整大小。这将使NSView保持在您以编程方式指定的起点,从而保持对接口构建器的覆盖不变。你这样做:

[<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:

其中 将是NSView的实例,即self.button。第一行声明该视图不具有大小,而第二行声明该掩码应该被自动布局系统视为约束。你的修订AppDelegate。m将:

#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.

更新:如果您使用这个方法来设置隐藏的NSView,那么当帧的父视图/窗口被调整大小时,自动布局系统仍然会考虑隐藏的NSView的框架。这意味着,如果隐藏的NSView在调整大小后将位于超视图的可见区域之外,那么自动布局系统将阻止超视图正确调整大小,而将强制超视图框架将隐藏的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:

对于这个问题,一个简单的解决方案是在设置hidden后将NSView的宽度和高度设置为0:YES,并在设置hidden之前恢复宽度和高度:NO。例如,在代码中的某个点使用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:

但是,如果在NSView中设置了宽度/高度自动布局约束(通过编程或通过接口构建器),这些更改可能会抛出以下警告:

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:

你可以忽略这些警告,减少约束优先级较低的数量,改变约束从=≤,或交替只需setTranslatesAutoresizingMaskIntoConstraints:没有隐藏NSView之前,并设置它是的调NSView之前:

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

And when we unhide the NSView:

当我们打开NSView时

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

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

您还可以通过子类化NSView和重写setHidden来实现自动化:(注意!)

- (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.

然后你可以简单地调用[self]。按钮setHidden:是的);或自我。按钮setHidden:没有);重写的方法会处理所有的事情。

#2


2  

From awakeFromNib description:

从awakeFromNib描述:

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.

因为不能保证从归档实例化对象的顺序,所以初始化方法不应该向层次结构中的其他对象发送消息。可以从awakeFromNib方法中安全地发送到其他对象的消息。通常,对于需要额外设置但在设计时无法完成的对象,您可以实现awakeFromNib。例如,您可以使用此方法定制任何控件的默认配置,以匹配用户首选项或其他控件中的值。您还可以使用它将各个控件恢复到应用程序的某些先前状态。

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:

方法。此外,您不应该忘记调用super的方法——在某些情况下,它可能非常重要。所以你的最终代码应该是这样的:

@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

更新:谢谢来电。看来你的观点是自动调整的。您应该检查autoresizingMask和自动布局约束

#3


1  

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

因此,我通过对视图进行子类化和重写-resizeWithOldSuperviewSize来阻止视图的大小调整:什么都不做,如下所示:

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