Cocoa: Rotation in Scrollable Document Views

I’m continuing work on my mac project, which involves a scalable document that can be embedded in an NSScrollView. I recently wanted to add orientation to the set of valid transformations. While I’m sure there are more generic solutions that involve setting a composite affine transformation on the underlying CALayer, I wanted something that I could implement in an hour or so. I ran into some issues that I thought I’d write about in hopes of saving other people some time.

When coding against the SDK for OS X Lion, NSView has a setFrameCenterRotation: selector, which is very convenient for rotating about the center of the view. If you need to run against older versions of OS X, you can use a combination of setFrameOrigin: and setFrameRotation: to accomplish the same thing. These rotations don’t affect the internal coordinate system of the NSView, but change its frame in the superview – also very convenient when it comes to performing layout in the rotated view.

I experimented with these for my document view’s transformations and found some quirks. Firstly, I tried to simply rotate the NSScrollView’s documentView. From my previous post, this is the HostView object. That didn’t go quite as well as I had hoped: the rotation occurred around the incorrect locus, and the scroll view got very confused about positioning. When I performed the rotation, the document would jump to an odd place, and then when I moved the scroll bars, the document would jump back to the origin.

After that, I attempted to rotate the _docView inside the HostView object. Success! At least, initial success: while the rotation occurred and the document stayed stable inside the scroll view, the dimensions of the HostView object stayed the same and the _docView was positioned incorrectly. I found that the NSViewFrameDidChangeNotification was still being raised. When I analyzed the newly calculated frame for the HostView, I found the width and height were the same as the original, when it should have been changed.

Changing the code in updateFrame: to detect the _docView in landscape orientation and transforming the frame appropriately makes the scroll view recalculate its scroll extents correctly. There’s still the problem of the incorrectly positioned document. I’m not sure why Cocoa does this, but when calculating the frame for a rotation, the view would consistently get shifted by a size proportional to the size of the view. I had to manually adjust the frame after the rotation had adjusted it.

After all is said and done, the new code looks like this.