2013-03-21 28 views
5

Ich mache eine kleine Server-App für OS X und ich benutze eine NSTextView, um einige Informationen über verbundene Clients zu protokollieren.Scrollen NSTextView nach unten

Immer, wenn ich brauche etwas anmelden Ich anhängt die neue Nachricht an den Text des NSTextView auf diese Weise:

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

Danach ich die NSTextField möchte (oder vielleicht sollte ich das NSClipView sagen, dass enthält es), um nach unten zu scrollen, um die letzte Zeile seines Textes anzuzeigen (offensichtlich sollte es nur scrollen, wenn die letzte Zeile noch nicht sichtbar ist, tatsächlich, wenn dann neue Zeile die erste Zeile ist, die ich logge, ist es bereits auf dem Bildschirm, also gibt es) kein Bedarf nach unten zu scrollen).

Wie kann ich das programmatisch?

Antwort

11

gefunden Lösung:

- (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)]; 
} 
+0

in Ordnung, aber ich habe nicht die Notwendigkeit für die „smart“ BOOL blättern sehen. Erstens sollte der Operator im Ausdruck für BOOL-Bildlauf! = Statt == sein. Sinn macht, und! = Funktioniert für mich, aber == funktioniert nicht. Zweitens, wenn ich eine Textzeile hinzufüge, die in einer neuen Zeile endet, dann zeigt es manchmal die neue Zeile und manchmal nicht. Ich sehe nicht, warum wir jemals "nicht bis zum Ende des Inhalts der Textansicht scrollen" wollen. Das wollen wir. Auf alle Fälle. Ich habe die if (scroll) Zeile entfernt und es funktioniert gut. Vielleicht testen wir mit entgegengesetzten Fällen :)) –

+1

Seien Sie vorsichtig mit "[self.textView scrollRangeToVisible: NSMakeRange (self.textView.string.length, 0)]". Dies kann nicht wirklich nach unten scrollen (abhängig vom Layout Ihrer NSTextView).) Wenn die Höhe der NSTextView nicht gleichmäßig durch die Höhe Ihrer Textzeilen teilbar ist, schneidet sie wahrscheinlich die untere Textzeile ab (in diesem Fall funktioniert das intelligente Scrollen nicht.) Besser zu verwenden (Swift-Beispiel) : "self.textView.scrollToVisible (NSRect (x: 0, y: self.textView.frame.height-1, width: self.textView.frame.width, height: 1))". – pauln

+0

Auch ich fand diese leichte Änderung an der Scroll - Flagge hilfreich, die Ihnen ein wenig Spielraum gibt, so dass Sie nicht genau am Ende sein müssen (Swift - Beispiel): 'let scroll = abs (self.logTextView.visibleRect. maxY - self.logTextView.bounds.maxY) pauln

1

Ich habe mit diesem für eine Weile durcheinander, weil ich es nicht zuverlässig arbeiten konnte. Ich habe endlich meinen Code funktioniert, also möchte ich ihn als Antwort posten.

Mit meiner Lösung können Sie manuell scrollen, während die Ausgabe zur Ansicht hinzugefügt wird. Sobald Sie bis zum absoluten Ende der NSTextView blättern, wird das automatische Scrollen fortgesetzt (falls aktiviert).

zunächst eine Kategorie dies nur für #import bei Bedarf ...

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 

... schaffen Sie sich eine " OutputView ", die eine Unterklasse von NSTextView ist:

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 

... Sie können ein paar mehr Komfort Methoden dieser Klasse hinzufügen (ich es abgestreift habe nach unten), so dass man ausgeben kann eine formatierte Zeichenfolge.

- (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); 
} 
5

Ab OS 10.6 ist es so einfach wie nsTextView.scrollToEndOfDocument(self).

+0

Danke! Sie können auch nil anstelle von self übergeben. –

+0

Ich stimme zu, dass dies in der Praxis funktioniert, aber ich finde keinen Hinweis darauf in der Dokumentation von Apple, die zeigt, dass diese NSResponder-Methode von NSTextView offiziell implementiert (und unterstützt) wird. Können Sie bitte einen Link angeben? – pauln

+0

Ich kann nicht glauben, dass ich seit zwei Tagen damit rumgespielt habe .... Vielen Dank! – BadgerBadger

0

Ich habe einige maßgeschneiderte NSTextView und individuelle Eingabemethode so meine Option zu verwenden, war:

self.scrollView.contentView.scroll(NSPoint(x: 1, y: self.textView.frame.size.height))