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 に答える
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

... NSTextView のサブクラスである「OutputView」を自分で作成します。

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 に答える