NSSplitView 和自动布局

Posted

技术标签:

【中文标题】NSSplitView 和自动布局【英文标题】:NSSplitView and autolayout 【发布时间】:2012-06-29 12:48:24 【问题描述】:

我应该如何在NSSplitView 子视图中使用自动布局约束?

我的NSSplitView 子视图有 3 个子视图:topPanetableContainerbottomPane,我将约束设置如下:

NSDictionary* views = NSDictionaryOfVariableBindings(topPane, tableContainer, bottomPane);

for (NSView* view in [views allValues]) 
    [view setTranslatesAutoresizingMaskIntoConstraints:NO];


[myView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[topPane(34)][tableContainer][bottomPane(24)]|"
                                                               options:0 
                                                               metrics:nil 
                                                                 views:views]];

[mySplitView addSubview:myView];

在控制台中得到了这个:

Unable to simultaneously satisfy constraints:
(
    "<NSLayoutConstraint:0x7fd6c4b1f770 V:[NSScrollView:0x7fd6c4b234c0]-(0)-[CPane:0x7fd6c4b2fd10]>",
    "<NSLayoutConstraint:0x7fd6c4b30910 V:[CPane:0x7fd6c4b2f870(34)]>",
    "<NSLayoutConstraint:0x7fd6c4b30770 V:|-(0)-[CPane:0x7fd6c4b2f870]   (Names: '|':NSView:0x7fd6c4b22e50 )>",
    "<NSLayoutConstraint:0x7fd6c4b212f0 V:[CPane:0x7fd6c4b2fd10]-(0)-|   (Names: '|':NSView:0x7fd6c4b22e50 )>",
    "<NSLayoutConstraint:0x7fd6c4b2f910 V:[CPane:0x7fd6c4b2f870]-(0)-[NSScrollView:0x7fd6c4b234c0]>",
    "<NSLayoutConstraint:0x7fd6c4b21290 V:[CPane:0x7fd6c4b2fd10(24)]>",
    "<NSAutoresizingMaskLayoutConstraint:0x7fd6c3630430 h=--& v=--& V:[NSView:0x7fd6c4b22e50(0)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7fd6c4b1f770 V:[NSScrollView:0x7fd6c4b234c0]-(0)-[CPane:0x7fd6c4b2fd10]>

我认为&lt;NSAutoresizingMaskLayoutConstraint:0x7fd6c3630430 h=--&amp; v=--&amp; V:[NSView:0x7fd6c4b22e50(0)]&gt; 会导致此问题,但我无法重置自动调整大小掩码,因为NSSplitView 设置了它。

在拆分视图中使用自动布局的最佳方式是什么?有没有办法在没有NSSplitViewDelegate的情况下处理具有自动布局的拆分视图子视图的最小/最大大小?

【问题讨论】:

这里有同样的问题。我将所有内容都放在 IB 中而不是以编程方式,但有类似的调试输出,包括 NSAutoresizingMaskLayoutConstraint 这似乎在 10.8 下已修复,但正如您在 10.7 下所指出的那样已损坏。在 10.8 中,您可以在 Xcode 中设置拆分视图的内容视图的最小高度和宽度(无论如何是 4.5.2)。在 10.7 下无法执行此操作,并且在 10.8 中创建的应用程序在 10.7 中仍然无法正常工作 通常与 10.8+ 一起工作,但需要在大多数视图的子视图之间指定约束 - 不是 supre - 否则你会得到 Unable to 同时满足 错误.. 【参考方案1】:

我发现如果我的窗口中有 工具栏 并通过以下任何委托方法控制拆分视图,则会出现此错误:

splitView:constrainMinCoordinate:ofSubviewAt:   
splitView:constrainMaxCoordinate:ofSubviewAt:
splitView:shouldAdjustSizeOfSubview:

解决方法是在 windowDidLoad 中将工具栏附加到窗口。

【讨论】:

我在 NSTabView 上遇到了同样的问题,取消选中工具栏的 Visible at Launch 复选框,然后让它在 windowDidLoad 中可见确实摆脱了愚蠢的警告。感谢您的提示。 在具有最小/最大宽度约束的基于自动布局的拆分视图中,我遇到了同样的问题。删除所有这 3 个委托方法解决了它。 关于这个问题的很多错误信息。这个答案是在正确的轨道上,但工具栏业务是一条红鲱鱼。该问题记录在 AppKit 发行说明 (developer.apple.com/library/content/releasenotes/AppKit/…) 中;一些NSSplitViewDelegate 方法不能很好地与自动布局配合使用,不应使用。相反,在NSSplitView 正下方的NSViews 上设置适当的约束,它应该可以在不记录的情况下正常工作。【参考方案2】:

NSSplitView 从一开始就是一件奇怪的事情,如果它很快就会消失,我也不会感到惊讶。在尝试让 NSSplitView 与 AutoLayout 一起工作一个月后,从一次绝望攻击陷入另一次绝望攻击后,我终于放弃了。

我的解决方案是根本不使用带有 AutoLayout 的 NSSplitView。所以无论是不带 Autolayout 的 NSSplitView 还是不带 NSSplitView 的 Autolayout:这并不像听起来那么复杂:只需将您的子视图彼此相邻布置并添加 NSLayoutConstraintsIBOutlets。然后可以在代码中从控制器设置和更改这些约束的常量。使用这种方法,您可以设置原点(将其滑出窗口的负偏移量)、宽度以及与其他子视图的关系——此外,使用视图的动画器对约束进行动画处理非常容易(曾经尝试过 animate em> 一个 NSSplitView?)

唯一缺少的是鼠标拖动分隔线,但这可以通过几行来实现,在您的自定义“SplitView”中跟踪 mouseEvents。

Apple 提供了一个自动布局“splitview”示例(不幸的是只有垂直),我最近在 github 上至少看到了一个新项目。虽然对我来说,我认为针对我的应用的特定需求使用我的自定义解决方案重新开始会更容易,而不是尝试创建一些非常通用的东西(从而使其过于复杂而无法处理)。

编辑:我现在完成了从单独的 nib 加载其子视图的自定义 splitView。没有约束问题,没有自动布局警告。与试图让它与 NSSplitView 一起工作的整个月相比,我现在有了一个基于约束的工作自定义 splitView,易于动画化,仅在一个晚上创建。我绝对推荐走这条路!

【讨论】:

@Ben-Uri:我的answer 中有一个小项目可能对你有用。 @Jay - 虽然这个答案是在我运行 10.7 时编写的。它仍然有效。正如我之前所说的——它对于简单的布局来说确实足够了,但是一旦你使用更复杂的自动布局,NSSplitView 肯定会出现问题。只需尝试添加调整大小和折叠视图,然后尝试为它们设置动画,祝你好运。 @auco - 刚刚使用股票 NSSplitView 和自动布局实现了一个类似于 Xcode Inspector 的视图。没有问题,折叠/展开/添加使用自动布局免费动画的子视图..正如我所说的,至少在 10.8+ 中它就像微风一样工作 不想发牢骚,但我真的很想知道这些持续的匿名反对票(自从我在 2012 年撰写此答案以来至少有四次)。如果您愿意加入带有论据和经验的讨论和/或添加或支持另一个解决方案,我会发现它更有帮助 - 这显然不会发生,因为对其他答案的支持数量没有改变。这个话题的存在和非常有偏见的观点表明 NSSplitView 和 Autolayout 存在一些问题,至少在与 Mac OS 10.7 兼容时是这样。很重要。【参考方案3】:

对于将来偶然发现此问题并正在寻找基于约束的 NSSplitView 替代品的快速启动,我在这里编写了一个小项目,尝试使用 Auto 重新创建 NSSplitView 的一部分功能布局:

https://github.com/jwilling/JWSplitView

它有些错误,但对于任何想要走这条路的人来说,它可能是一个有用的参考。

【讨论】:

【参考方案4】:

10.8 修复了该问题,请参阅其发行说明。

这是我的 10.7 解决方案(自定义拆分视图): https://github.com/benuri/HASplitView.git

【讨论】:

【参考方案5】:

您根本不想禁用translatesAutoresizingMaskIntoConstraints。您不应该混淆系统视图约束。 NSSplitView 处理各个视图本身的大小,您实际上是在试图剥夺它的控制权。更不用说,你忘了考虑拆分器。

在拆分视图上设置最小或最大(或恒定)宽度/高度的正确方法是在视图上单独设置这些内容。特别是,如果您在代码中执行此操作,则需要使用 2 次单独调用 constraintsWithVisualFormat,因为否则视觉格式语言将在视图之间创建约束。

您可以在 IB 中完成所有这些工作。您甚至可以在拆分视图中设置每个视图的优先级,这将导致一个或另一个视图在窗口调整大小时调整大小,而不是平均分配调整大小。

【讨论】:

很高兴听到你建议如何在 IB 中打开 AutoLayout 来完成这一切...... 实际上在 10.7 中不可能 - 在 10.7 下的 Xcode 4.5.2 中禁用了在 NSSplitView 内容视图上设置宽度约束的控件,如果您在 10.8 上的 Xcode 中设置它然后运行应用程序10.7 它将打破该约束,支持 10.7 中的 NSAutoresizingMaskLayerConstraint,因此不起作用。【参考方案6】:

尽管我讨厌不同意,但 Auco 的回答不应该被评为最高。通过足够的工作量来解决问题在任何方面都没有帮助。在我看来,NSSplitView 只是那些没有充分阅读文档的人的问题。

这里提到的问题的实际解决方案相当简单:Auto Layout 在 NSSplitView 上引入了新的“Holding Priorities API”。正如文档所说:将较低的值设置为子视图的持有优先级将使他更有可能更早地采用宽度。所有这些都可以在 IB 中以编程方式设置,而不会感到绝望。所需工作量:约 20 秒。

【讨论】:

这个答案如果简单地回答问题会更有用,而不是对其他回答者和提问者表现出如此蔑视。我会给你 +1 的答案和 -1 的粗鲁,所以他们互相抵消。 @Jacque:如果您还阅读了文档,您会发现保持优先级仅适用于 OS X 10.8+。当他们可能正在考虑向后兼容性而您没有时,没有必要对他人不体谅。 @Jacque:如果您正在处理一个非常简单的布局,例如两个 textView 作为一个 splitView 的子视图,您可能是对的。但是,一旦您需要处理复杂的布局子视图,设置保持优先级就无济于事:使用自动布局的 splitView 中的内容会产生自动布局错误,因为它与自动调整大小的掩码相冲突。为 SplitView 设置动画是一件很麻烦的事情,因为您需要计算帧数,这在使用自动布局时是不行的。仅凭优先级就无法解决的问题确实多得多。【参考方案7】:

我通过一个 nib 文件加载所有内容,然后setTranslatesAutoresizingMaskIntoConstraints:NO

因此,也许您应该首先通过[mySplitView addSubview:myView]; 添加您的视图,然后禁用将自动调整大小的掩码转换为约束,然后将您的约束添加到myView

编辑:

好吧,看来我误解了 myView。 您必须将约束添加到子视图而不是拆分视图。

[topPane addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topPane(34)]" options:0 metrics:nil views:views]];

[bottomPane addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[bottomPane(24)]" options:0 metrics:nil views:views]];

您不必添加边缘约束(“V:|[topPane(34)]”中的“|”),因为 NSSplitView 中的子视图已经自动调整大小。

这导致了这个,例如对于 topPane 约束:

注意:忽略子视图内容,它们只是占位符

【讨论】:

如果我将translatesAutoresizingMaskIntoConstraints 设置为NOmyView,拆分视图将无法处理它的大小 - 它不适用于自动布局。【参考方案8】:

我花了一些时间让我的自动布局清除警告,但我确实在 IB 中处理了它(几个拆分视图和子视图)。

我的布局如下:

根视图 |--第一个 NSSplitView(3 个垂直子视图) |----UIView(左) |----第二个 NSSplitView(中心和 2 个水平子视图) |---UIView(顶部) |---第 3 个 NSSplitView(底部和 3 个垂直子视图) |---UIView(左) |---UIView(居中) |---UIView(右) |----UIView(右)

我的问题是,我的所有子视图中都有 19 个警告,但我的布局看起来很好,并且工作正常。 过了一会儿,我找到了警告的原因:我的第一个拆分视图中外部视图的约束。

两个视图(左视图和右视图)都具有“宽度 >= 200”的宽度约束,而中心视图(第二个拆分视图)没有约束(因为其最小宽度和最大宽度由其子视图处理)。

警告告诉我,自动布局想要缩小我的 IB-UI-Layout,因为计算出的最小宽度小于我的布局,但我不想在 IB 中缩小它。

我为我的第一个拆分视图的两个外部子视图添加了一个固定约束“宽度 = 200”,并选中了“在构建时删除”。

现在我的布局没有警告,一切正常。

我的结论

我认为自动布局和拆分视图的问题是自动布局无法处理子视图的宽度约束。我们想要使用拆分视图的原因是,我们想要视图的动态宽度,并且我们想要它在两个方向上,收缩和扩展。

所以没有宽度 = xxx 。 Autolayout 只能处理其中一个,我们会在 IB 中收到警告。您可以使用 IB 中的临时约束来解决此问题,该约束将在运行时之前删除。

我希望我写的内容有意义,但它在我的项目中运行良好。

PS:直到今天我找到了这个帖子,我才找到任何解决方案。所以我猜你的帖子给了我灵感:-)

【讨论】:

【参考方案9】:

我使用这个类作为解决方法,它并不完美(子视图有点卡顿)但它让我畅通无阻。我使用这个类作为每个拆分视图窗格中的自定义类。

@interface FBSplitPaneView : NSView

@end

@implementation FBSplitPaneView

- (void)setFrame:(NSRect)frame

  for (NSView *subview in self.subviews) 
    subview.frame = self.bounds;
  
  [super setFrame:frame];


@end

【讨论】:

以上是关于NSSplitView 和自动布局的主要内容,如果未能解决你的问题,请参考以下文章

按钮和自动布局

使用自动布局删除和重新添加子视图

Xcode 自动布局和旋转

滚动视图和 UI 组件的通用自动布局

自动布局和 ios5

自定义 tableviewcell 和自动布局