使用自动布局自动换行NSTextField

时间:2014-07-07 19:52:22

标签: cocoa autolayout appkit

当NSTextField的宽度发生变化时,如何让自动布局自动将NSTextField包装到多行?

我有许多NSTextField在检查器窗格中显示静态文本(即:标签)。当用户调整检查器窗格的大小时,如果需要,我希望右侧标签能够回流到多行。

(Finder的“获取信息”面板执行此操作。)

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

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

现状:

Text fields not wrapping.

我希望发生什么:

enter image description here

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

更新1.0

考虑到@ hamstergene的建议,我将NSTextField子类化并制作了一个示例应用程序。在大多数情况下,它现在可以工作,但现在有一个小的布局问题,我怀疑这是因为NSTextField的框架与自动布局所期望的完全不同步。在下面的屏幕截图中,右侧标签全部垂直间隔,并带有top约束。在调整窗口大小时,Where字段将正确调整大小并进行包装。但是,在我再次调整窗口“再多一个像素”之前,Kind文本字段不会被按下。

enter image description here 示例:如果我将窗口调整到恰当的宽度,Where文本字段首先进行换行,那么我会在中间图像中得到结果。如果我再向窗口调整一个像素,则会正确设置Kind字段的垂直位置。

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

假设这是问题,我如何告知我在setFrameSize中进行的这些更改的自动布局,以便它可以再次运行布局。 (而且,最重要的是,不要陷入layout-setFrameSize-layout-etc.的递归状态......)

解决方案

我想出了一个似乎与我希望的完全一致的解决方案。我只是覆盖了NSTextFieldlayout的{​​{1}},而不是继承superview。在NSTextField中,我在文本字段上设置layout,然后触发布局传递。这似乎足以使它大部分工作,但它留下了布局短暂“错误”的恼人问题。 (见上面的注释)。

解决方法似乎是调用preferredMaxLayoutWidth,然后调用一切正常工作。

setNeedsDisplay

4 个答案:

答案 0 :(得分: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 :(得分:6)

如果检查器窗格宽度永远不会改变,只需选中IB中的“First Runtime Layout Width”(注意它的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;
}

在任何一种情况下,必须以明显的方式设置约束(你可能已经尝试过了):高垂直拥抱优先级,低水平,将所有四个边都固定到superview和/或兄弟视图。

答案 2 :(得分:1)

在文本字段首选宽度部分的尺寸检查器选项卡中设置为"第一个运行时布局宽度"

答案 3 :(得分:0)

这对我有用,而且更优雅。另外,我在Github

上做了一个小示例项目
public class DynamicTextField: NSTextField {
   public override var intrinsicContentSize: NSSize {
      if cell!.wraps {
         let fictionalBounds = NSRect(x: bounds.minX, y: bounds.minY, width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
         return cell!.cellSize(forBounds: fictionalBounds)
      } else {
         return super.intrinsicContentSize
      }
   }

   public override func textDidChange(_ notification: Notification) {
      super.textDidChange(notification)

      if cell!.wraps {
         validatingEditing()
         invalidateIntrinsicContentSize()
      }
   }
}