NSTextView scrolling issue with vertical ruler and no wrapping

I have a NSTextView enclosed in a NSScrollView > NSClipView.

The NSScrollView has a vertical NSRulerView.

The NSTextView is configured to disable text wrapping, so for long and wide texts, there are both a horizontal scroller and a vertical scroller.

Problem:

When the text view is scrolled up or down using the page up or down keys, the text is correctly scrolled up or down BUT it is also scrolled to the left.

Basically, it's also scrolled to the left as if there was no vertical ruler.

Before the page down scroll:

Code Block language
+---+----------------------------+-+
| | Lorem ipsum dolor sit amet,| |
| | Lorem ipsum dolor sit amet,| |
| | Lorem ipsum dolor sit amet,|-|
| | Lorem ipsum dolor sit amet,| |
| | Lorem ipsum dolor sit amet,| |
| | Lorem ipsum dolor sit amet,| |
| | Lorem ipsum dolor sit amet,| |
| | Lorem ipsum dolor sit amet,| |
| | Lorem ipsum dolor sit amet,| |
| | Lorem ipsum dolor sit amet,| |
| | Lorem ipsum dolor sit amet,| |
| | Lorem ipsum dolor sit amet,| |
| | Lorem ipsum dolor sit amet,| |
| | Lorem ipsum dolor sit amet,| |
| +--+-------------------------+-+
+---+--+-------------------------+-+


After the page down scroll:

Code Block language
+---+----------------------------+-+
| | m ipsum dolor sit amet, con| |
| | m ipsum dolor sit amet, con| |
| | m ipsum dolor sit amet, con| |
| | m ipsum dolor sit amet, con| |
| | m ipsum dolor sit amet, con|-|
| | m ipsum dolor sit amet, con| |
| | m ipsum dolor sit amet, con| |
| | m ipsum dolor sit amet, con|-|
| | m ipsum dolor sit amet, con| |
| | m ipsum dolor sit amet, con| |
| | m ipsum dolor sit amet, con| |
| | m ipsum dolor sit amet, con| |
| | m ipsum dolor sit amet, con| |
| | m ipsum dolor sit amet, con| |
| +--+--+----------------------+-+
+---+--+--+----------------------+-+


The next page down or page up scroll will not scroll the text horizontally.

I looked into the different APIs related to scrolling in NSView, NSScrollView, NSText, NSTextView and my guess would be that the problem happens when the scrollToPoint: method is run for the NSTextView because of an incorrect convertPoint: fromView: conversion.

BTW, switching between contiguous and non contiguous for the NSTextView does not fix the issue.

I have not found any WWDC session so far that would deal with that issue (WWDC 2010 Session 214 does not AFAIK).

Google Search, Stack Overflow are clueless.

Of course, this issue does not happen when the NSTextView wraps text. There is no horizontal scroller in this case.

Question:

How can you prevent NSTextView from scrolling incorrectly in this case?

Replies

Some additional info:
  • using the vertical scroller with the mouse to scroll vertically just works fine (dragging the thumb or clicking below the thumb).

  • using the Home and End keys just works fine.

  1. I think it's a bug in AppKit that exists in 10.14.x, 10.15.x and 11.0.1.

The NSTextView scrolling methods do not seem to take into account vertical rulers and horizontal scrolling at the same time by assuming the scroll point will always have x = 0 for key scrolling that goes through the scrollDown: and scrollUp: methods.

2. The following code looks like to address the issue and work for 10.14 and later (on Intel at least). It can certainly be improved.

Code Block Obj-C
- (void)_scrollDown:(CGFloat)inOffset
{
    NSScrollView * tScrollView=self.enclosingScrollView;
    NSView * tDocumentView=tScrollView.documentView;
    NSRulerView * tVerticalRulerView=tScrollView.verticalRulerView;
    if (self!=tDocumentView
        tVerticalRulerView==nil
        tScrollView.horizontalScroller.isHidden==YES)
    {
        [super _scrollDown:inOffset];
        return;
    }
    NSRect tOldFrame=tDocumentView.frame;
    
    /* Offset the text view frame to take into account the vertical ruler width */
        
    NSRect tNewFrame=tOldFrame;
        
    tNewFrame.origin.x-=NSWidth(tVerticalRulerView.frame);
        
    self.frame=tNewFrame;
    
    [super _scrollDown:inOffset];
    /* Restore the text view frame */
    self.frame=tOldFrame;
}