【问题标题】:NSView Frame Reverting After Being Set Programmatically以编程方式设置后的 NSView 帧恢复
【发布时间】:2014-02-18 23:34:42
【问题描述】:

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

编辑:

这是一个显示问题的简单示例(IB中的原始帧是{{20, 118}, {48, 48}}):

AppDelegate.m:

#import "AppDelegate.h"

@implementation AppDelegate

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

@end

日志:

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}}

编辑#2:

在我编辑框架时调用堆栈(来自原始应用):

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
)

从帧恢复时调用堆栈:

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
)

如果我需要发布堆栈中使用的方法中的代码以使其有用,请告诉我。 (对不起,我以前从未真正处理过这些东西)

【问题讨论】:

  • 请发布一个调用堆栈,其中 setFrame 被错误数据调用
  • 可能你从错误的地方调用你的帧更新。但是您没有向我提供其他信息。所以我帮不了你。
  • 改变框架是什么意思?是大小,位置? -1
  • @Avt 我一直在尝试以一种不受我正在处理的问题的大程序的约束的方式重新创建这种情况。我想来自较大程序的所有调用都将无济于事,但正如我所说,我无法找出问题的根源。当我可以将其简化为更简单的内容时,我会更新。
  • 您可能在视图上设置了自动布局约束,它们会自动布局视图。但是,是的,显示调用堆栈会确认。在您的日志语句中包含[NSThread callStackSymbols]

标签: objective-c cocoa interface-builder frame nsview


【解决方案1】:

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

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

&lt;id&gt; 将是您的 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

更新: 如果您将此方法与您计划设置为hidden 的 NSView 一起使用,当调整框架的超级视图/窗口大小时,自动布局系统仍会考虑隐藏的 NSView 的框架。这意味着如果隐藏的 NSView 在调整大小后超出了父视图的可见区域,自动布局系统将阻止父视图正确调整大小,而是强制父视图框架包围隐藏的 NSView。

解决这个问题的一个简单方法是在设置hidden:YES 之后将NSView 的宽度和高度设置为零,并在设置hidden:NO 之前恢复宽度和高度。例如,在您的代码中的某个时刻使用您的 NSView self.button:

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

及以后:

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

但是,如果您在 NSView 上设置了宽度/高度自动布局约束(以编程方式或通过 Interface Builder),这些更改可能会引发如下警告:

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)]>

您可以忽略这些警告,将约束优先级降低到较低的数字,将约束从 = 更改为 ,或者您也可以在之前简单地 setTranslatesAutoresizingMaskIntoConstraints:NO隐藏 NSView,并在取消隐藏 NSView 之前将其设置为 YES

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

当我们取消隐藏 NSView 时:

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

您还可以通过继承 NSView 并覆盖 setHidden:(注意 !)来自动执行此操作:

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

然后您可以简单地调用[self.button setHidden:YES];[self.button setHidden:NO];,重写的方法将处理所有事情。

【讨论】:

    【解决方案2】:

    来自 awakeFromNib 描述:

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

    我不是 100% 确定,但我强烈建议搬家

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

    - (void)viewDidLoad
    

    方法。你也不应该忘记调用 super 的方法——在某些情况下它可能很关键。所以你的最终代码应该是这样的:

    @implementation AppDelegate
    
    - (void)awakeFromNib{
        [super awakeFromNib];
        ... non GUI initialization
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        [self.button setFrame:NSMakeRect(50, 10, 100, 100)];
    }
    
    @end
    

    更新: 感谢电话卡住。您的视图似乎是自动调整大小的。您应该检查 autoresizingMask 和自动布局约束

    【讨论】:

    • 谢谢,但我认为这并不能解决我的问题,因为在我正在开发的更大的应用程序中,即使从-awakeFromNib 以外的地方调用-setFrame: 也会发生这种情况。我已使用原始应用程序中的调用堆栈更新了我的问题。
    • 感谢电话卡住。您的视图似乎是自动调整大小的。您应该检查 autoresizingMask 和自动布局约束
    • 我已经尝试将 autoreziseMask 设置为 NSViewNotSizeable (这似乎没有效果)并摆脱所有约束(它将初始框架设置为与 IB 完全不同的东西,但仍然有它自动调整大小。我还尝试将超级视图的 -setAuotresizesSubviews: 设置为 NO,这也没有做任何事情。您对如何阻止视图自动调整大小有任何想法吗?autoresizingMask 返回 12,唯一的约束是 &lt;NSContentSizeLayoutConstraint:0x101c21f10 V:[FrameLogProgressIndicator:0x10183e450(12)] Hug:250 CompressionResistance:750&gt;
    【解决方案3】:

    所以我设法通过子类化视图并覆​​盖 -resizeWithOldSuperviewSize: 来停止调整视图大小,就像这样:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-05
      • 2017-10-13
      • 1970-01-01
      相关资源
      最近更新 更多