views:

300

answers:

2

I have a NSTextView that I want to display a horizontal scroll bar. Following some leads on the internet, I have most of it working, except that I am having problems with the vertical scroll bar.

What I have done is to find the width of the longest line (in pixels with the given font) and then I resize the NSTextContainer and the NSTextView appropriately. This way the horizontal scroll bar is representative of the width and scrolling to the right will scroll to the end of the longest line of text.

After doing this work, I noticed that my NSScrollView would show and hide the vertical scroll bar as I typed. I've 'fixed' this problem by setting autohidesScrollers to NO before the resize and then YES afterwards. However, there still remains another problem where, as I type, the vertical scrollbar thumb jumps to the top of the scrollbar and back to the proper place as I type. I type 'a' <space>, it jumps to the top, I press the <space> again and it jumps back to the proper location.

Any thoughts?

Here is some sample code:

- (CGFloat)longestLineOfText
{
    CGFloat longestLineOfText = 0.0;

    NSRange lineRange;

    NSString* theScriptText = [myTextView string];

    NSDictionary* attributesDict = [NSDictionary dictionaryWithObject:scriptFont forKey:NSFontAttributeName]; //scriptFont is a instance variable

    NSUInteger characterIndex = 0;
    NSUInteger stringLength = [theScriptText length];

    while (characterIndex < stringLength) {
     lineRange = [theScriptText lineRangeForRange:NSMakeRange(characterIndex, 0)];

     NSSize lineSize = [[theScriptText substringWithRange:lineRange] sizeWithAttributes:attributesDict];
     longestLineOfText = max(longestLineOfText, lineSize.width);

     characterIndex = NSMaxRange(lineRange);
    }

    return longestLineOfText;

}

// ----------------------------------------------------------------------------

- (void)updateMyTextViewWidth
{
    static CGFloat previousLongestLineOfText = 0.0;

    CGFloat currentLongestLineOfText = [self longestLineOfText];
    if (currentLongestLineOfText != previousLongestLineOfText) {
     BOOL shouldStopBlinkingScrollBar = (previousLongestLineOfText < currentLongestLineOfText);
     previousLongestLineOfText = currentLongestLineOfText;

     NSTextContainer* container = [myTextView textContainer];
     NSScrollView* scrollView = [myTextView enclosingScrollView];
     if (shouldStopBlinkingScrollBar) {
      [scrollView setAutohidesScrollers:NO];
     }

     CGFloat padding = [container lineFragmentPadding];

     NSSize size = [container containerSize];
     size.width = currentLongestLineOfText + padding * 2;
     [container setContainerSize:size];

     NSRect frame = [myTextView frame];
     frame.size.width = currentLongestLineOfText + padding * 2;
     [myTextView setFrame:frame];

     if (shouldStopBlinkingScrollBar) {
      [scrollView setAutohidesScrollers:YES];
     }
    } 
}
A: 

You should probably ask this on cocoa-dev. It's likely to get an answer from the guys who maintain the Cocoa text system there.

NSResponder
I did that 1 minute after posting this question to stackoverflow. :)
Lyndsey Ferguson
A: 

Thanks to Ross Carter's post on the Cocoa-Dev list, I resolved this issue.

A. You have to set up your text view to support horizontal scrolling:

- (void)awakeFromNib {
    [myTextView setHorizontallyResizable:YES];
    NSSize tcSize = [[myTextView textContainer] containerSize];
    tcSize.width = FLT_MAX;
    [[myTextView textContainer] setContainerSize:tcSize];
    [[myTextView textContainer] setWidthTracksTextView:NO];
}

B. You have to update the width of the text view as it changes, otherwise the horizontal scroll-bar doesn't update properly:

- (void)textDidChange:(NSNotification *)notification
{
    [self updateTextViewWidth];
}

- (CGFloat)longestLineOfText
{
    CGFloat longestLineOfText = 0.0;

    NSLayoutManager* layoutManager = [myTextView layoutManager];

    NSRange lineRange;
    NSUInteger glyphIndex = 0;
    NSUInteger glyphCount = [layoutManager numberOfGlyphs];
    while (glyphIndex < glyphCount) {

     NSRect lineRect = [layoutManager lineFragmentUsedRectForGlyphAtIndex:glyphIndex
                    effectiveRange:&lineRange
                 withoutAdditionalLayout:YES];

     longestLineOfText = max(longestLineOfText, lineRect.size.width);

     glyphIndex = NSMaxRange(lineRange);
    }

    return longestLineOfText;

}

// ----------------------------------------------------------------------------

- (void)updateTextViewWidth
{
    static CGFloat previousLongestLineOfText = 0.0;

    CGFloat currentLongestLineOfText = [self longestLineOfText];
    if (currentLongestLineOfText != previousLongestLineOfText) {
     previousLongestLineOfText = currentLongestLineOfText;

     NSTextContainer* container = [myTextView textContainer];
     CGFloat padding = [container lineFragmentPadding];

     NSRect frame = [myTextView frame];
     frame.size.width = currentLongestLineOfText + padding * 2;
     [myTextView setFrame:frame];
    }
}
Lyndsey Ferguson