一旦用户键入足够的文本以溢出控件的宽度(如本文所述),我正试图让我的 NSTextField 的高度增加(很像在 iChat 或 Adium 中)
我已经接受了接受的答案,但我似乎无法让它发挥作用。我已经在http://scottyob.com/pub/autoGrowingExample.zip上传了我的尝试
理想情况下,当文本增长时,包含窗口应该随之增长,但我在这里尝试了一些小步骤。
一旦用户键入足够的文本以溢出控件的宽度(如本文所述),我正试图让我的 NSTextField 的高度增加(很像在 iChat 或 Adium 中)
我已经接受了接受的答案,但我似乎无法让它发挥作用。我已经在http://scottyob.com/pub/autoGrowingExample.zip上传了我的尝试
理想情况下,当文本增长时,包含窗口应该随之增长,但我在这里尝试了一些小步骤。
阅读Apple 文档通常很有帮助。Apple 设计了所有这些文本布局的东西,使其功能强大到足以处理各种复杂的边缘情况,这有时非常有用,有时却没有。
首先,我将文本字段设置为在分词时换行,所以我们实际上得到了多行。(您的示例代码甚至有一个 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
就不会存在,而且你可能必须自己设置框架,计算新的原点和新的大小(不要太困难,但只是更多的代码)。
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
还有一个小问题:输入空格时,文本会向左跳一点。但是,这在我的应用程序中不是问题,因为在大多数情况下,文本字段不应包含空格。
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
}
}
}
此解决方案也适用于设置文本字段的字符串值以及通过 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()
}
}
如果你想限制 TextField 的大小(例如):
if (intrinsicSize.height > 100)
{
intrinsicSize = _lastIntrinsicSize;
}
else
{
_lastIntrinsicSize = intrinsicSize;
_hasLastIntrinsicSize = YES;
}
(差异)
我遇到的一件事是将 NSTextField 嵌入到 NSScrollView 中并让它正常工作(尤其是在 NSStackView 中)。将看看使用 NSTextView 是否会更容易。