2014-07-07 40 views
6

如何让自动布局自动将NSTextField换行为NSTextField的宽度更改为多行?使用自动布局自动换行NSTextField

我有许多NSTextField在检查器窗格中显示静态文本(即:标签)。由于用户调整了检查器窗格的大小,因此如果需要,我希望右侧标签可以重新排列成多行。

(Finder的获取信息面板做到这一点。)

但我一直无法弄清楚的自动布局约束的组合,以允许这种行为。在所有情况下,右侧的NSTextFields拒绝包装。 (除非我明确添加允许的高度约束)

视图层次结构是这样的:每个灰色带都是包含两个NSTextField的视图,左侧的属性名称和右侧的属性值。当用户调整检查器窗格的大小时,我希望属性值标签根据需要自动调整其高度。

现状:

Text fields not wrapping.

我想什么有发生:

enter image description here

(请注意,这种行为比大多数堆栈溢出问题,不同的我遇到关于NSTextFields来到和自动布局,这些问题希望文本字段在用户输入时增长,在这种情况下,文本是静态的,并且NSTextField被配置为查看就像一个标签。)

更新1.0

以@ hamstergene的建议下,我子类的NSTextField,并提出了小样本应用程序。大多数情况下,它现在可以工作,但现在有一个小的布局问题,我怀疑是由于NSTextField的框架与自动布局所期望的不完全同步的结果。在下面的屏幕截图中,右侧标签全部在垂直方向上间隔排列,约束条件为top。当窗口调整大小时,Where字段得到正确调整和包装。然而,Kind文本字段不会被推下来,直到我调整窗口“多一个像素”。

enter image description here 例子:如果我调整窗口的大小,以恰到好处的宽度,该文本框Where做它的第一个包,然后我得到的中间图像中的结果。如果我调整窗口的大小再多一个像素,那么Kind字段的垂直位置就会被正确设置。

我怀疑这是因为自动布局正在通过,然后帧被明确设置。我想象一下,自动布局并没有看到那次通过,而是在下一次通过时做到这一点,并相应地更新位置。

假设是这个问题,我如何通知setFrameSize我正在做这些更改的自动布局,以便它可以再次运行布局。 (而且,最重要的是,不会陷入布局set-setSize-layout-recursive状态......)

解决方案

我拿出这似乎工作,我究竟是如何希望的解决方案。而不是继承NSTextField,我只是覆盖layoutNSTextFieldsuperview。在layout内,我在文本字段上设置preferredMaxLayoutWidth,然后触发布局传递。这似乎足以让它主要工作,但它留下了令人讨厌的布局问题短暂“错误”。 (见上面的注释)。

解决方案似乎是打电话setNeedsDisplay,然后一切正常工作。

- (void)layout { 
     NSTextField *textField = ...; 
     NSRect oldTextFieldFrame = textField.frame; 

     [textField setPreferredMaxLayoutWidth:NSWidth(self.bounds) - NSMinX(textField.frame) - 12.0]; 

     [super layout]; 

     NSRect newTextFieldFrame = textField.frame; 
     if (oldTextFieldFrame.size.height != newTextFieldFrame.size.height) { 
     [self setNeedsDisplay:YES]; 
     } 
    } 

回答

4

如果检查窗格宽度不会改变,只是检查“首先运行版式宽度”在IB(注意这是10.8+功能)。

但是,允许检查员同时具有可变宽度不可能仅凭约束来实现。 AutoLayout中的某处存在一个弱点。

我能够通过继承这样的文本字段,以实现可靠的行为:

- (NSSize) intrinsicContentSize; 
{ 
    const CGFloat magic = -4; 

    NSSize rv; 
    if ([[self cell] wraps] && self.frame.size.height > 1) 
     rv = [[self cell] cellSizeForBounds:NSMakeRect(0, 0, self.bounds.size.width + magic, 20000)]; 
    else 
     rv = [super intrinsicContentSize]; 
    return rv; 
} 

- (void) layout; 
{ 
    [super layout]; 
    [self invalidateWordWrappedContentSizeIfNeeded]; 
} 

- (void) setFrameSize:(NSSize)newSize; 
{ 
    [super setFrameSize:newSize]; 
    [self invalidateWordWrappedContentSizeIfNeeded]; 
} 

- (void) invalidateWordWrappedContentSizeIfNeeded; 
{ 
    NSSize a = m_previousIntrinsicContentSize; 
    NSSize b = self.intrinsicContentSize; 
    if (!NSEqualSizes(a, b)) 
    { 
     [self invalidateIntrinsicContentSize]; 
    } 
    m_previousIntrinsicContentSize = b; 
} 

在这两种情况下,约束必须设置明显的方式(你可能已经尝试过):高垂直拥抱优先级,低水平,将所有四条边连接到超视图和/或同级视图。

+0

检查员的宽度是可变的并且是用户控制的。它不受任何约束的控制(也没有定义)。截图中只有灰色视图和NSTextField标签正在使用自动布局。 (所有这些都是在代码中完成的,而不是在Interface Builder中完成的。)我将重新访问子类并在'layout'中手动执行布局,但我希望仅使用约束来缩小Finder的“获取信息”面板中显示的包装行为。 – kennyc

+1

最后,setPreferredMaxLayoutWidth是获得解决方案的关键 – kennyc

+0

“第一次运行时布局宽度”对我来说有窍门。 – dkrdennis

7

得到这个工作,最简单的方式,假设您使用的是基于NSViewController解决方案是这样的:

- (void)viewDidLayout { 
    [super viewDidLayout]; 
    self.aTextField.preferredMaxLayoutWidth = self.aTextField.frame.size.width; 
    [self.view layoutSubtreeIfNeeded]; 
} 

这只是让约束系统解决宽度(高度将在此运行不可解所以将会是你最初设置的),然后你将这个宽度作为最大布局宽度并且执行另一个基于约束的布局传递。

没有子类化,没有使用视图的布局方法,没有通知。如果您没有使用NSViewController,则可以调整此解决方案,以便在大多数情况下可以工作(在自定义视图中对文本字段进行子类化等)。

其中大部分来自膨胀http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html(查看多行文本的内部大小部分)。

1

设置在部分文本字段首选宽度尺寸检查选项卡以“第一运行时布局宽度”