helftone robots

Infinite NSScrollView

by Milen

Infinitely scrollable areas are a common problem when implementing apps which provide a never-ending canvas. Let's take a look how we can do it using NSScrollView.

Approach

First up, there is no such thing as an "infinite" scroll view, as ultimately, we have a finite amount of memory. When we refer to infinity, what we mean is that in practical terms the user will never hit an edge. One way to achieve that would be to make the scrollable area as large as possible (max values of CGFloat). Unfortunately, such very large values end up producing jerky scrolling movements.

Another approach is to never let the user hit the edge of the scroll view. We can observe the position of the scroll area and if it gets too close to a particular edge, we can move it back to the center while also making any changes to ensure the onscreen contents do not change. This will create the illusion of an infinite scroll view – in practice, the user can just never hit the edge.

Geometry

NSScrollView works slightly differently compared to UIScrollView. We have a clip view which contains a document view. The document view is the user-provided and contains custom content. The total scrollable area is defined by the size of the document view and scrolling happens by moving the bounds origin of the clip view.

What we need to do is observe the geometry of the clip view and whenever it gets close to the edge, re-center it.

The clip view defines the region of the document that's visible in the scroll view. This is achieved by adjusting the clip view bounds. The total scrollable area is, by definition, the document view frame. Created with Monodraw.

Recenter

Once we have settled on an approach, we have reduced the problem to deciding exactly when to recenter. On iOS, we could use -layoutSubviews but that method only works on OS X if Auto Layout is used (which raises other issues). Instead, we're going to observe the bounds property of the clip view and recenter when we receive the notification.

When the clip view gets close to the edge, we move it back to the center. We have to also move the document view bounds to compensate for the movement and ensure there is no visual change. Created with Monodraw.

If you tried to do that, you will stumble upon a bug in NSScrollView as the notification is sent synchronously and the NSScrollView method is not re-entrant. To workaround the issue, we enqueue a block to do the recentering at the next runloop iteration.

We're not done, yet. OS X perform smooth scrolling if you use a mouse wheel but unfortunately, there's a bug in the implementation and it cannot handle any movement of the bounds origin during smooth scrolling. Thankfully, we can easily workaround the bug by overriding NSClipView's -scrollToPoint: to simply forward to -setBoundsOrigin:.

Enough talking, just go and grab the code!

← Back to Blog