【问题标题】:getting a NSTextField to grow with the text in auto layout?让 NSTextField 与自动布局中的文本一起增长?
【发布时间】:2013-01-01 02:05:42
【问题描述】:

一旦用户输入足够多的文本以超出控件的宽度(如on this post 所问),我正试图让我的 NSTextField 增加其高度(很像在 iChat 或 Adium 中)

我已经暗示了接受的答案,但我似乎无法让它发挥作用。我已将我的尝试上传到http://scottyob.com/pub/autoGrowingExample.zip

理想情况下,当文本增长时,包含窗口应该随之增长,但我在这里尝试了一些小步骤。

【问题讨论】:

  • 只是一个建议,你认为如果你使用 NSTextView 代替会更容易吗?
  • “我似乎无法让它工作”是什么意思?相反会发生什么?你跑的是 Lion 还是 Mountain Lion?
  • 刚刚在 Lion 上尝试过。它有效(感谢您从上一个问题中借用的子类),但仅当用户按 Enter 或结束编辑时。我尝试打开连续,但没有帮助。
  • 这与@PeterHosey 发生在我身上的事情是一样的。很接近,但没有我需要的效果
  • 有人知道如何使用NSTextView 做到这一点吗?

标签: macos cocoa nstextfield autolayout


【解决方案1】:

解决了! (灵感来自https://github.com/jerrykrinock/CategoriesObjC/blob/master/NS(Attributed)String%2BGeometrics/NS(Attributed)String%2BGeometrics.m

阅读Apple Documentation 通常会有所帮助。 Apple 设计了所有这些文本布局,使其功能强大到足以处理各种complicated edge cases,这有时非常有用,有时却没有。

首先,我将文本字段设置为在分词时换行,因此我们实际上得到了多行。 (您的示例代码甚至有一个 if 语句,因此在关闭换行时它什么也不做)。

这个技巧是要注意,当文本被编辑时,它是由“字段编辑器”打印的——一个重量级的 NSTextView 对象,由 NSWindow 拥有,它被 NSTextField 所重用目前是“第一响应者”(已选择)。 NSTextView 有一个 NSTextContainer(文本所在的矩形),它有一个 NSLayoutManager 来布局文本。我们可以询问布局管理器要使用多少空间,以获得我们文本字段的新高度。

另一个技巧是重写 NSText 委托方法 - (void)textDidChange:(NSNotification *)notification 以在文本更改时使内在内容大小无效(因此它不会在您通过按返回提交更改时等待更新)。

我没有按照您最初的建议使用 cellSizeForBounds 的原因是我无法解决您的问题 - 即使在使单元格的固有内容大小无效时,cellSizeForBounds: 仍会继续返回旧大小。

GitHub 上找到示例项目。

@interface TSTTextGrowth()
{
    BOOL _hasLastIntrinsicSize;
    BOOL _isEditing;
    NSSize _lastIntrinsicSize;
}

@end

@implementation TSTTextGrowth

- (void)textDidBeginEditing:(NSNotification *)notification
{
    [super textDidBeginEditing:notification];
    _isEditing = YES;
}

- (void)textDidEndEditing:(NSNotification *)notification
{
    [super textDidEndEditing:notification];
    _isEditing = NO;
}

- (void)textDidChange:(NSNotification *)notification
{
    [super textDidChange:notification];
    [self invalidateIntrinsicContentSize];
}

-(NSSize)intrinsicContentSize
{
    NSSize intrinsicSize = _lastIntrinsicSize;

    // Only update the size if we’re editing the text, or if we’ve not set it yet
    // If we try and update it while another text field is selected, it may shrink back down to only the size of one line (for some reason?)
    if(_isEditing || !_hasLastIntrinsicSize)
    {
        intrinsicSize = [super intrinsicContentSize];

        // If we’re being edited, get the shared NSTextView field editor, so we can get more info
        NSText *fieldEditor = [self.window fieldEditor:NO forObject:self];
        if([fieldEditor isKindOfClass:[NSTextView class]])
        {
            NSTextView *textView = (NSTextView *)fieldEditor;
            NSRect usedRect = [textView.textContainer.layoutManager usedRectForTextContainer:textView.textContainer];

            usedRect.size.height += 5.0; // magic number! (the field editor TextView is offset within the NSTextField. It’s easy to get the space above (it’s origin), but it’s difficult to get the default spacing for the bottom, as we may be changing the height

            intrinsicSize.height = usedRect.size.height;
        }

        _lastIntrinsicSize = intrinsicSize;
        _hasLastIntrinsicSize = YES;
    }

    return intrinsicSize;
}

@end

最后一点,我自己从未真正使用过自动布局 - 演示看起来很棒,但每当我自己实际尝试时,我都无法让它完全正常工作,这让事情变得更加复杂。但是,在这种情况下,我认为它确实节省了很多工作 - 没有它,-intrinsicContentSize 将不存在,您可能必须自己设置框架,计算新原点以及新尺寸(不是太难,只是更多的代码)。

【讨论】:

  • 似乎我们不需要无条件地使内在内容大小无效 - sample code
  • 这是有史以来最好的解决方案。谢谢。
  • 虽然此解决方案有效,但存在一个问题:文本字段始终以单行的高度开始,即使文本跨越多行也是如此。只有当您第一次编辑文本时,高度才能正确调整。有人对此有解决方案吗?
  • 我在尝试将此解决方案应用于继承自 NSTextFieldNSTokenField 时遇到了这个问题。看看the super simple alternative I came up with
【解决方案2】:

DouglasHeriot 的解决方案仅适用于固定宽度的文本字段。在我的应用程序中,我有想要水平和垂直增长的文本字段。因此我将解决方案修改如下:

AutosizingTextField.h

@interface AutosizingTextField : NSTextField {
    BOOL isEditing;
}
@end

AutosizingTextField.m

@implementation AutosizingTextField

- (void)textDidBeginEditing:(NSNotification *)notification
{
    [super textDidBeginEditing:notification];
    isEditing = YES;
}

- (void)textDidEndEditing:(NSNotification *)notification
{
    [super textDidEndEditing:notification];
    isEditing = NO;
}

- (void)textDidChange:(NSNotification *)notification
{
    [super textDidChange:notification];
    [self invalidateIntrinsicContentSize];
}

-(NSSize)intrinsicContentSize
{
    if(isEditing)
    {
        NSText *fieldEditor = [self.window fieldEditor:NO forObject:self];
        if(fieldEditor)
        {
            NSTextFieldCell *cellCopy = [self.cell copy];
            cellCopy.stringValue = fieldEditor.string;
            return [cellCopy cellSize];
        }
    }
    return [self.cell cellSize];
}
@end

还有一个小问题:输入空格时,文本会向左跳一点。不过,这在我的应用中不是问题,因为在大多数情况下,文本字段不应包含空格。

【讨论】:

  • 计算效果很好。既然NSControlcell 属性在10.11 中已被弃用,您打算如何获得cellSize
  • 在字段编辑器上调用-invalidateTextContainerOrigin可以解决文本左移的问题。我在-setFrame:setFrameSize: 中调用[self.currentEditor invalidateTextContainerOrigin; 之前 在我的NSTextField 子类中调用super。
【解决方案3】:

DouglasHeriot 的解决方案非常适合我。

这是 Swift 4 上的相同代码

class GrowingTextField: NSTextField {

    var editing = false
    var lastIntrinsicSize = NSSize.zero
    var hasLastIntrinsicSize = false

    override func textDidBeginEditing(_ notification: Notification) {
        super.textDidBeginEditing(notification)
        editing = true
    }

    override func textDidEndEditing(_ notification: Notification) {
        super.textDidEndEditing(notification)
        editing = false
    }

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

    override var intrinsicContentSize: NSSize {
        get {
            var intrinsicSize = lastIntrinsicSize

            if editing || !hasLastIntrinsicSize {

                intrinsicSize = super.intrinsicContentSize

                // If we’re being edited, get the shared NSTextView field editor, so we can get more info
                if let textView = self.window?.fieldEditor(false, for: self) as? NSTextView, let textContainer = textView.textContainer, var usedRect = textView.textContainer?.layoutManager?.usedRect(for: textContainer) {
                    usedRect.size.height += 5.0 // magic number! (the field editor TextView is offset within the NSTextField. It’s easy to get the space above (it’s origin), but it’s difficult to get the default spacing for the bottom, as we may be changing the height
                    intrinsicSize.height = usedRect.size.height
                }

                lastIntrinsicSize = intrinsicSize
                hasLastIntrinsicSize = true
            }

            return intrinsicSize
        }
    }
}

【讨论】:

    【解决方案4】:

    此解决方案也适用于设置文本字段的字符串值以及通过 AutoLayout 调整其大小时。它只是在需要时使用属性文本属性来计算内在内容大小。

    class AutoGrowingTextField: NSTextField {
        var maximumHeight: CGFloat = 100
    
        override var intrinsicContentSize: NSSize {
            let height = attributedStringValue.boundingRect(
                with: NSSize(width: bounds.width - 8, height: maximumHeight),
                options: [NSString.DrawingOptions.usesLineFragmentOrigin]
                ).height + 5
    
            return NSSize(width: NSView.noIntrinsicMetric, height: height)
        }
    
        override func textDidChange(_ notification: Notification) {
            super.textDidChange(notification)
            invalidateIntrinsicContentSize()
        }
    
        override func layout() {
            super.layout()
            invalidateIntrinsicContentSize()
        }
    }
    

    【讨论】:

    • 您的解决方案在 macOS big Sur 上运行,而 NSLayoutManager 解决方案根本无法运行。谢谢。
    【解决方案5】:

    如果你想限制 TextField 的大小(例如):

    if (intrinsicSize.height > 100) 
    {
        intrinsicSize = _lastIntrinsicSize;
    } 
    else
    {
        _lastIntrinsicSize = intrinsicSize;
        _hasLastIntrinsicSize = YES;
    }
    

    (Diff)

    我遇到的一个问题是将 NSTextField 嵌入到 NSScrollView 中并让它正常工作(尤其是在 NSStackView 中)。看看用 NSTextView 会不会更容易。

    【讨论】:

      【解决方案6】:

      我想出了一个适合我的替代解决方案:

      - (NSSize)intrinsicContentSize {
          return [self sizeThatFits:NSMakeSize(self.frame.size.width, CGFLOAT_MAX)];
      }
      
      - (void)textDidChange:(NSNotification *)notification {
          [super textDidChange:notification];
          [self invalidateIntrinsicContentSize];
      }
      

      基本上,我们只是将宽度限制为元素的给定宽度,并据此计算拟合高度。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多