Byłem brudząc przy tym na chwilę, bo nie mogłem zmusić go do pracy w wiarygodny sposób. W końcu udało mi się uruchomić mój kod, więc chciałbym opublikować go jako odpowiedź.
Moje rozwiązanie umożliwia przewijanie ręczne, podczas gdy dane wyjściowe są dodawane do widoku. Jak tylko przewiniesz do absolutnego dołu NSTextView, automatyczne przewijanie zostanie wznowione (jeśli jest włączone).
Pierwsza kategoria do #import to tylko w razie potrzeby ...
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
... utwórz własne " OutputView ", który jest podklasą 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
... Można dodać kilka więcej metod wygody do tej klasy (mam pozbawiono go w dół), tak, że można wyprowadzać sformatowany ciąg.
- (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);
}
OK, ale nie widzę potrzebę „inteligentne” BOOL zwoju. Po pierwsze, w wyrażeniu do przewijania BOOL, operator powinien być! = Zamiast ==. Ma sens, a! = Działa dla mnie, ale == nie działa. Po drugie, jeśli dodaję jeden wiersz tekstu, kończąc na znaku nowej linii, to czasami pokazuje on znak nowej linii, a czasem nie. Nie rozumiem, dlaczego kiedykolwiek * nie * chcemy "Przewiń do końca zawartości w tekście". Tego właśnie chcemy. We wszystkich przypadkach. Usunąłem linię if (scroll) i działa dobrze. Być może testujemy z przypadkami przeciwległymi :)) –
Uważaj przy użyciu "[self.textView scrollRangeToVisible: NSMakeRange (self.textView.string.length, 0)]". To nie może faktycznie przewinąć do dołu (w zależności od układu twojego NSTextView.Jeśli wysokość NSTextView nie jest równomiernie podzielona przez wysokość linii tekstu, prawdopodobnie częściowo odetnie dolną linię tekstu (w takim przypadku inteligentne przewijanie nie zadziała.) Lepiej używać (przykład Swift) : "self.textView.scrollToVisible (NSRect (x: 0, y: self.textView.frame.height-1, width: self.textView.frame.width, height: 1))". – pauln
Zauważyłem również, że ta drobna modyfikacja flagi przewijania jest pomocna, co daje trochę luzu, więc nie musisz być dokładnie na dole (przykład Swift): 'let scroll = abs (self.logTextView.visibleRect. maxY - self.logTextView.bounds.maxY)
pauln