2013-03-21 36 views
5

Tôi đang tạo một ứng dụng máy chủ nhỏ cho OS X và tôi đang sử dụng NSTextView để đăng nhập một số thông tin về các khách hàng được kết nối.Cuộn NSTextView xuống dưới

Bất cứ khi nào tôi cần phải đăng nhập cái gì tôi đang phụ thêm các tin nhắn mới đến nội dung của NSTextView theo cách này:

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

Sau này tôi muốn các NSTextField (hoặc có lẽ tôi nên nói rằng NSClipView chứa nó) để cuộn xuống để hiển thị dòng cuối cùng của văn bản của nó (rõ ràng là nó sẽ cuộn chỉ khi dòng cuối cùng không nhìn thấy được, trên thực tế nếu dòng mới đó là dòng đầu tiên tôi ghi nó đã có trên màn hình để có không cần phải cuộn xuống).

Tôi làm cách nào để lập trình?

Trả lời

11

giải pháp Tìm thấy:

- (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

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

+1

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

+0

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

1

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

Kể từ HĐH 10.6 nó đơn giản như nsTextView.scrollToEndOfDocument(self).

+0

Cảm ơn! Bạn cũng có thể vượt qua nil, thay vì tự. –

+0

Tôi đồng ý công việc này trong thực tế, nhưng tôi không thể tìm thấy bất kỳ tham chiếu nào trong tài liệu này của Apple cho thấy phương pháp NSResponder này được NSTextView chính thức triển khai (và hỗ trợ). Bạn có thể vui lòng cung cấp liên kết không? – pauln

+0

Tôi không thể tin rằng tôi đã rối tung với điều này trong hai ngày .... Cảm ơn rất nhiều! – BadgerBadger

0

Tôi có một số tùy chỉnh NSTextView và đầu vào tùy chỉnh phương pháp để lựa chọn của tôi là sử dụng:

self.scrollView.contentView.scroll(NSPoint(x: 1, y: self.textView.frame.size.height)) 
Các vấn đề liên quan