Problem solving: The strange case of the scrolling SpriteKit View

(Xcode 9.3; macOS 10.12.6, Swift 4)

If you want a challenge, I recommend you try to implement ‘scrolling SpriteKitView’ yourself before you read the rest of this article.

Update (and spoiler): Due to a bug in InterfaceBuilder’s ‘SKView’, you need to use a ‘custom View’ (NSView) in Interface Builder, and change its type to SKView. Using the inbuilt SKView leads to extremely poor performance.

This isn’t really a tutorial, but it’s an insight into the development process, and the kind of post I desperately needed when I was starting out and had no idea how to solve challenges. Consequently, this will include the blind alleys and failed tries. (I’m documenting these in part because the scrolling SKView I worked out in Xcode 8 no longer works when I recreate the probject in Xcode 9; this might break again in the future.)

I’m cheating somewhat in that this is a streamlined version of my process, and I tried a couple of things several times because I didn’t believe the results and felt I must have done something wrong, and it didn’t occur to me initially to set up a scroll view with known-to-work content. As it turned out after I had a working version, the version I had at the end product proved to be not fully usable (it would display a static scene but fail to work properly once interactive elements were added), so I went back, played with SKView a lot, and ended up with an InterfaceBuilder-based scrollable view that does not rely on creating an SKView in code.

The challenge is seemingly trivial: I am working on an application that will use SpriteKit. It is not a game, and thus I want it to behave like ordinary desktop apps: users will want to resize windows. And thus I want to use a SpriteKit view that can be scrolled.
I have a talent for finding challenges that sound like they should be trivial, but turn out to be far more complex than I had bargained for. This is one of them.

0a) Start with a standard Game template application, build and run.


Nothing special here at all. In GameScene.sks, you can jazz up the label a bit: different font, different colour, different text. *Do* set a different background for the scene: changing the background colour isn’t just because I hate staring at black: if you have a valid SpriteKit view, but no valid scene, it will show as featureless black, so a non-black scene is very obviously working.

0b) Create a new viewController scene, and create an NSViewController subclass: ScrollableViewController. Add this scene as the WindowController’s content, build and run, admire a calming light grey window.

1) We want a scrollView, so let’s start with the most basic case: putting something different into the scrollview, and watching it scroll. I find that this is a very useful way of working: I do write a fair amount of code that I run once and delete once I’ve confirmed it’s working, but it’s extremely reassuring to have code fail early.

So, let’s add an image to the asset catalogue and an image view to our project. Make the image a large one, since we want to scroll: mine is 2688 × 1792px. My settings are scaling: none and size: 150x120px.

1a) Build and run:
As expected, a fraction of a flamingo.

1b) We want a scroll view. Let’s go to Editor -> Embed in -> ScrollView. Build and run.


That’s a no, then. The scroll bars turn up, but they don’t work, and all that’s displayed is the same excerpt as before.

Did Apple just break scroll views? I’ve used them before, and they’ve always worked.

1c) So let’s go to the documentation and to Apple’s examples (that seems like overkill for something like scroll views, and I already knew what I usually do to get a working scrollview, but let’s pretend I did not.)

Creating a scroll view in Interface Builder is straightforward.

Apple has a ScrollView Programming Guide for Mac, which says:

Create the view, or views, that will be the document view of the scroll view.
Select that view, or views, that will be the scroll view’s document view.
Choose Layout > Make subviews of > Scroll View. This creates a new NSScrollView instance with the selected view, or views, as its document view.
Open the inspector and configure the visible scrollers, background color, and line and page scroll amounts.

It was last updated in June 2010, these commands are no longer available in Xcode (the Editor -> Embed In has been around for a while, but should be functionally equivalent), and the code is worse: it’s manually memory-managed ObjectiveC, so we can delete this from our list of useful resources.

Apple uses NSScrollView in a number of applications. I chose PhotoEditor, for being a modern app that works perfectly fine, so no, scroll views aren’t broken. What is it doing that I am not?


Using a lot of custom classes, including for the ClipView, which I’ve never seen done before, so that wasn’t a useful example for me.

1d) So let’s go back to ‘I’ve done this before and it worked’. This is really where experience comes in: I’ve done this thing before, I have a working example on my hard drive, plus I know how I usually set up scrollViews: by dragging a scrollView into the scene, and then adding an image view. So let’s do that.

I’m adding a scrollview, and pinning it to the window; then adding an imageView with the same settings as above and pinning it to its superview with 0/0/0/0. Straight away, we get an autolayout weirdness: Autolayout REALLY does not like those settings.


Build and run anyway: this is what we want. The image is fully scrollable. But what’s up with autolayout? In IB, it looks like this:

which almost looks as if it expects the imageView to be the full size of the image, and Xcode complains about missing x and y constraints for the view (not: the imageView).

Spoiler: if you let it add missing constraints, it adds the top and leading constraints, and this works great. If you pin the view to all sides of the scrollview, it will expand to the full size of the image and Not Scroll.

Anyway, we have a working scrollView.

2) So, let’s go back to SpriteKit. In a perfect world, we follow the steps we used to create a scrolling ImageView and end up with a scrolling SpriteKit view. How hard can it be?

2a) Clean up the GameScene.swift file. Remove everything to do with labels, animation, and reacting to mouse clicks and, well, everything.

import SpriteKit

class GameScene: SKScene {

Create a new viewController scene, set its controller class to ‘ViewController’, add an SKView from the object library, connect it to the ViewController outlet, build and run.



Right. Connect the WindowController’s contentView to the initial scene, build and run.


So we have a setup that works, and a setup that seems identical, but which does not.

2b) Let’s look at both of these again.


Here we have the opposite problem to the scrollview one: if we add the SKView to the view, nothing happens, if we make it the viewController’s view, it works just fine.

Is there a relevance to the fact that in the original scene, the SKView is displayed like an NSView, but in ours it has a separate icon and exposes more settings in IB?


2c) Who knows.

ed. to add: It turns out that, in a bizarre bug, using an NSView cast to SKView in InterfaceBuilder – rather than an SKView – is vital for presenting the SKScene from the viewController (and for getting access to all of the interaction I’d taken out for the purpose of this tutorial). I will write this up properly in a separate post.

So let’s try again with a new ViewController scene, and this time drag the SKView on top of the scene’s ‘view’. (If you do this, it will show the SKView icon and expose the settings above. If you change the class to ‘SKView’ in the Identity inspector, you’ll get the NSView icon and fewer options. Make sure that you connect the ViewController’s view outlet to this view as well as the skView outlet.

Build and run… oh.

On the positive side, we have a working SpriteKit view.

On the negative side, it doesn’t display the content we are looking for. I have found this unreliable: some of the SpriteKit views I’ve dragged into my app have the sceneName set to the default GameScene.sks; others – like this one – do not. If you’re getting a black screen as above, check that setting in the Attributes inspector. Now build and run:


2d) With this in mind, I’ve tried to look at the Add SKView scene again, but there are no more settings that can be changed, and that version remains frustratingly empty.

There is not much point in embedding our SKView outlet it into a scroll view if it won’t show up at all.

At this point it should be abundantly clear why I am a) creating new scenes for every step instead of messing about with the old one (it’s a form of version control; I have real version control, too) and b) testing every trivial step. Some of them yield surprising results, see the ’embed in scrollView’ action.

2e) There’s no point, but I want to see what happens anyway. Sometimes failures give something away, or rather, if I stare at it long enough, I can work out what’s going on; find the ultimate course.
New view controller, SKView, embed in scrollView, hook up to outlet, build and run: nope.


New viewController, SKView, scrollView from IB, add SKView, hook up outlet, build and run:


There’s *something* going on. In both cases, the SpriteKit view gets ignored completely; in the first example, the scrollView does not seem to have a content view, in the second, it does… but neither wants to know anything about SpriteKit.

3) Back to the drawing board.

Here, we are working with an outlet in IB, and unless it’s a ViewController’s contentView, the SKView gets ignored.

We’re also working with *modern* SpriteKit code, which means instantiating our GameScene from the GameScene.sks file, which adds a layer of complexity.

Since the application ignores the existence of our SKView, changing the GameScene instantiation to

let scene = SKScene(size: CGSize(width: 2688, height: 1792))

will have no effect, but adding the SKView in code sounds promising.

Update: This version led to a version that appeared to work and which was fine as a proof of concept. See point 4 for better practice.

3a) Let’s create a CodeViewController class:

class CodeViewController: NSViewController {

let skView = SKView(frame: NSRect(x: 0, y: 0, width: 2688, height: 1792))

@IBOutlet weak var scrollView: NSScrollView!

override func viewDidLoad() {


if let scene = SKScene(fileNamed: "GameScene") {
scene.scaleMode = .aspectFill

Build and run.


That’s somewhat promising! It doesn’t scroll yet, but we finally have an embedded SKView that actually shows our scene.

On the other hand, our scrollView has forgotten how to scroll.

3b) We know the solution to that one. As the very first thing we did to explore scrollviews, we used the ‘Embed in’ command which put our imageView as the scrollView’s subview but did not scroll.

So rather than adding our skView to the scrollview, let’s add it as a subview to the (correctly-sized) content view of our scrollview:



And there we have it: a fully scrolling SpriteKit view with minimum fuss and no errors. (An earlier version of this app threw up a strange one: unsupported surface format: LA08 about which I could find no further information.

3c) Troubleshooting hints:
– If you do the setup, but your content does not scroll, check that you’ve set the size of the scroll view’s content view to the size of your SpriteKit view – if it’s the size of the canvas in IB, all you’ll see is that small window.
– Autolayout can be interesting; it’s best to set up the scrolling SpriteKitView first, and then experiment with which items to pin where – getting that perfect is beyond the scope of this post, but I just want to flag that up as an item of interestingness. I’ve had the best luck with pinning the bottom of the content view to the scrollview rather than, as one would in Cocoa, the top.

4) Given the insights from my SKView post, I made a new attempt to embed SpriteKit in Interface Builder, using a CustomView cast to SKView.

This is the simplest and least complex way of creating a scrollable SpriteKit view: Draw an NSScrollView to the InterfaceBuilder Scene, and either set its NSView to become an SKView (and hook it up with the ViewController’s skView outlet) or embed a customView in the ScrollView and set it to SKView. Make sure that your .sks file has the corresponding .swift SKScene set as its custom class, and present the scene in the viewController’s skView outlet.

4a) The only thing that doesn’t work so well is scrolling to the centre point, if you want that: I found that the only way to make

scrollView.contentView.scroll(NSPoint(x: 1500, y: 1150))

work reliably is to make the window non-restorable (otherwise it moves to the last position of the scroll view, even after clean and between restarts). So you could do that for the first launch, focus the scrollView on the centre, and then turn on restorable.

This post, is, of course, a lie. I don’t think anyone consistently manages to think their way through problems with so little resistance. The path to the scrollview was nowhere near as smooth as I am pretending; this is the path I wish I’d taken. It didn’t occur to me that the non-scrolling problem could be a scrollview issue, and it wasn’t until I tried to embed an imageView in the scrollview that _that_ problem manifested itself obviously enough so I could diagnose and fix it. I also spent more time adding SpriteKit views as subviews and finding that much of the time, they either don’t show up or don’t display the scene. After that much flailing, it was nice to see that there is a nice, straightforward path of breaking down the problem into smaller pieces and testing each part of the hypothesis (if I do this… I expect that to happen), recording the differences (if I embed an imageView into a scrollview, I expect to have a scrollable imageView) and finding ways to solve that.

The question whether I will end up using a scrollable SpriteKit view in my final app is one I cannot answer right now; but this allows me to move forward and experiment.

The font I’m using is the marvellous 1651 Alchemy which is one of my favourite fonts. The flamingos are my own. Well, Chester Zoo’s.