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);
}
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 :)) –
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
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