Tôi đã rối tung với điều này trong một thời gian, bởi vì tôi không thể làm cho nó làm việc đáng tin cậy. Cuối cùng tôi đã nhận được mã của tôi làm việc, vì vậy tôi muốn đăng nó như là một câu trả lời.
Giải pháp của tôi cho phép bạn cuộn theo cách thủ công, trong khi đầu ra đang được thêm vào chế độ xem. Ngay sau khi bạn cuộn đến cuối tuyệt đối của NSTextView, cuộn tự động sẽ tiếp tục (nếu được bật, tức là).
Lần đầu tiên một loại để #import này chỉ khi cần thiết ...
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
... tạo cho mình một " OutputView ", là một lớp con của 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
... Bạn có thể thêm các phương pháp thuận tiện một vài chi tiết để lớp này (tôi đã lột nó xuống), do đó bạn có thể xuất một chuỗi định dạng.
- (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, nhưng tôi không thấy sự cần thiết của các "thông minh" BOOL cuộn. Thứ nhất, trong biểu thức cho cuộn BOOL, toán tử phải là! = Thay vì ==. Làm cho tinh thần, và! = Làm việc cho tôi, nhưng == không hoạt động. Thứ hai, nếu tôi thêm một dòng văn bản, kết thúc bằng một dòng mới, thì đôi khi nó hiển thị dòng mới và đôi khi nó không có. Tôi không thấy lý do tại sao chúng tôi sẽ bao giờ * không * muốn "Di chuyển đến cuối nội dung xem văn bản". Đó là những gì chúng tôi muốn. Trong tất cả trường hợp. Tôi đã xóa dòng if (scroll) và nó hoạt động tốt. Có lẽ chúng tôi đang thử nghiệm với các trường hợp cạnh đối diện :)) –
Hãy cẩn thận khi sử dụng "[self.textView scrollRangeToVisible: NSMakeRange (self.textView.string.length, 0)]". Điều này có thể không thực sự cuộn xuống phía dưới (tùy thuộc vào cách bố trí của NSTextView của bạn.) Nếu chiều cao của NSTextView không được chia đều cho chiều cao của dòng văn bản của bạn, nó có thể sẽ cắt một phần dòng dưới cùng của văn bản (trong trường hợp này, cuộn thông minh sẽ không hoạt động.) Tốt hơn để sử dụng (ví dụ Swift) : "self.textView.scrollToVisible (NSRect (x: 0, y: self.textView.frame.height-1, width: self.textView.frame.width, height: 1))". – pauln
Ngoài ra, tôi thấy sửa đổi nhỏ này với cờ cuộn hữu ích, điều này cho bạn một chút thiếu sót, do đó bạn không cần phải ở chính xác (ví dụ Swift): 'let scroll = abs (self.logTextView.visibleRect. maxY - self.logTextView.bounds.maxY)
pauln