14

我正在为 OS X 制作一个小服务器应用程序,并且正在使用 NSTextView 来记录有关已连接客户端的一些信息。

每当我需要记录某些内容时,我都会以这种方式将新消息附加到 NSTextView 的文本中:

- (void)logMessage:(NSString *)message
{
    if (message) {
        self.textView.string = [self.textView.string stringByAppendingFormat:@"%@\n",message];
    }
}

在此之后,我希望 NSTextField (或者我应该说包含它的 NSClipView )向下滚动以显示其文本的最后一行(显然它应该仅在最后一行不可见时滚动,事实上如果那时新行是我记录的第一行,它已经在屏幕上,因此无需向下滚动)。

我怎样才能以编程方式做到这一点?

4

5 回答 5

16

找到解决方案:

- (void)logMessage:(NSString *)message
{
    if (message) {
        [self appendMessage:message];
    }
}

- (void)appendMessage:(NSString *)message
{
    NSString *messageWithNewLine = [message stringByAppendingString:@"\n"];

    // Smart Scrolling
    BOOL scroll = (NSMaxY(self.textView.visibleRect) == NSMaxY(self.textView.bounds));

    // Append string to textview
    [self.textView.textStorage appendAttributedString:[[NSAttributedString alloc]initWithString:messageWithNewLine]];

    if (scroll) // Scroll to end of the textview contents
        [self.textView scrollRangeToVisible: NSMakeRange(self.textView.string.length, 0)];
}
于 2013-03-21T12:40:37.380 回答
13

从 OS 10.6 开始,它就像nsTextView.scrollToEndOfDocument(self).

于 2016-01-12T15:39:01.097 回答
6

迅捷 4 + 5

let smartScroll = self.textView.visibleRect.maxY == self.textView.bounds.maxY

self.textView.textStorage?.append("new text")

if smartScroll{
    self.textView.scrollToEndOfDocument(self)
}

于 2019-03-24T13:37:30.907 回答
2

我已经搞砸了一段时间,因为我无法让它可靠地工作。我终于让我的代码工作了,所以我想把它作为回复发布。

我的解决方案允许您手动滚动,同时将输出添加到视图中。一旦你滚动到 NSTextView 的绝对底部,自动滚动就会恢复(如果启用的话)。

首先是一个类别,仅在需要时才#import 这个...

FSScrollToBottomExtensions.h:

@interface NSView (FSScrollToBottomExtensions)
- (float)distanceToBottom;
- (BOOL)isAtBottom;
- (void)scrollToBottom;
@end

FSScrollToBottomExtensions.m:

@implementation NSView (FSScrollToBottomExtensions)
- (float)distanceToBottom
{
    NSRect  visRect;
    NSRect  boundsRect;

    visRect = [self visibleRect];
    boundsRect = [self bounds];
    return(NSMaxY(visRect) - NSMaxY(boundsRect));
}

// Apple's suggestion did not work for me.
- (BOOL)isAtBottom
{
    return([self distanceToBottom] == 0.0);
}

// The scrollToBottom method provided by Apple seems unreliable, so I wrote this one
- (void)scrollToBottom
{
    NSPoint     pt;
    id          scrollView;
    id          clipView;

    pt.x = 0;
    pt.y = 100000000000.0;

    scrollView = [self enclosingScrollView];
    clipView = [scrollView contentView];

    pt = [clipView constrainScrollPoint:pt];
    [clipView scrollToPoint:pt];
    [scrollView reflectScrolledClipView:clipView];
}
@end

...为自己创建一个“OutputView”,它是 NSTextView 的子类:

FSOutputView.h:

@interface FSOutputView : NSTextView
{
    BOOL                scrollToBottomPending;
}

FSOutputView.m:

@implementation FSOutputView

- (id)setup
{
    ...
    return(self);
}

- (id)initWithCoder:(NSCoder *)aCoder
{
    return([[super initWithCoder:aCoder] setup]);
}

- (id)initWithFrame:(NSRect)aFrame textContainer:(NSTextContainer *)aTextContainer
{
    return([[super initWithFrame:aFrame textContainer:aTextContainer] setup]);
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [super dealloc];
}

- (void)awakeFromNib
{
    NSNotificationCenter    *notificationCenter;
    NSView                  *view;

    // viewBoundsDidChange catches scrolling that happens when the caret
    // moves, and scrolling caused by pressing the scrollbar arrows.
    view = [self superview];
    [notificationCenter addObserver:self
    selector:@selector(viewBoundsDidChangeNotification:)
        name:NSViewBoundsDidChangeNotification object:view];
    [view setPostsBoundsChangedNotifications:YES];

    // viewFrameDidChange catches scrolling that happens because text
    // is inserted or deleted.
    // it also catches situations, where window resizing causes changes.
    [notificationCenter addObserver:self
        selector:@selector(viewFrameDidChangeNotification:)
        name:NSViewFrameDidChangeNotification object:self];
    [self setPostsFrameChangedNotifications:YES];

}

- (void)handleScrollToBottom
{
    if(scrollToBottomPending)
    {
        scrollToBottomPending = NO;
        [self scrollToBottom];
    }
}

- (void)viewBoundsDidChangeNotification:(NSNotification *)aNotification
{
    [self handleScrollToBottom];
}

- (void)viewFrameDidChangeNotification:(NSNotification *)aNotification
{
    [self handleScrollToBottom];
}

- (void)outputAttributedString:(NSAttributedString *)aAttributedString
    flags:(int)aFlags
{
    NSRange                     range;
    BOOL                        wasAtBottom;

    if(aAttributedString)
    {
        wasAtBottom = [self isAtBottom];

        range = [self selectedRange];
        if(aFlags & FSAppendString)
        {
            range = NSMakeRange([[self textStorage] length], 0);
        }
        if([self shouldChangeTextInRange:range
            replacementString:[aAttributedString string]])
        {
            [[self textStorage] beginEditing];
            [[self textStorage] replaceCharactersInRange:range
                withAttributedString:aAttributedString];
            [[self textStorage] endEditing];
        }

        range.location += [aAttributedString length];
        range.length = 0;
        if(!(aFlags & FSAppendString))
        {
            [self setSelectedRange:range];
        }

        if(wasAtBottom || (aFlags & FSForceScroll))
        {
            scrollToBottomPending = YES;
        }
    }
}
@end

...您可以向此类添加更多便捷方法(我已将其剥离),以便您可以输出格式化的字符串。

- (void)outputString:(NSString *)aFormatString arguments:(va_list)aArguments attributeKey:(NSString *)aKey flags:(int)aFlags
{
    NSMutableAttributedString   *str;

    str = [... generate attributed string from parameters ...];
    [self outputAttributedString:str flags:aFlags];
}

- (void)outputLineWithFormat:(NSString *)aFormatString, ...
{
    va_list         args;
    va_start(args, aFormatString);
    [self outputString:aFormatString arguments:args attributeKey:NULL flags:FSAddNewLine];
    va_end(args);
}
于 2015-02-24T23:34:06.273 回答
0

我有一些自定义的 NSTextView 和自定义输入法,所以我的选择是使用:

self.scrollView.contentView.scroll(NSPoint(x: 1, y: self.textView.frame.size.height))
于 2016-11-22T21:19:54.353 回答