1

Even in a minimal app, really minimal:

import Cocoa
import SpriteKit

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!
    @IBOutlet weak var skView: SKView! // in MainMeny.xib of the template SpriteKit app
}

If I change the resolution of the screen in System Preferences (e.g. from 1920 x 1080 to 1600 x 900, or back), the spinning beachball appears when I hover over the SKView and it is no longer possible to interact with the window (e.g. to resize it). Frustratingly, I get nothing in the console (either system or Xcode), nor does it happen every time though mostly it does. It makes no difference whether the view comes from a xib or if it is programmatically created.

Any clue would be very welcome.

I'd also be very grateful if anyone could confirm that they are seeing this too.

(building in debug Xcode 6.3.2 on OS X 10.10.3, both in debug and release)

Milos
  • 2,728
  • 22
  • 24

1 Answers1

0

As this is replicated with such a minimal app, and since no-one is responding, I'm forced to proceed assuming this is a SpriteKit bug. If people confirm they are seeing this too, I'll submit a bug report. In the meantime, I cannot submit my app now that I know it will crush anytime a user changes display configuration. To avoid the crash, however, proves to be rather tricky...

To begin with, the app crashes before NSApplication's applicationDidChangeScreenParameters(:) is called. To catch the event sooner than that we can use CGDisplayRegisterReconfigurationCallback. This is a C function whose first parameter (a CFunctionPointer) cannot at present be constructed in Swift. To use it, I had to descend to C and Objective-C:

DisplayConfigurationChanges.h

#import <Foundation/Foundation.h>

extern NSString * DisplayConfigurationWillChange;
extern NSString * DisplayConfigurationDidChange;

extern void StartNotificationsOnDisplayConfigurationChange();
extern void StopNotificationsOnDisplayConfigurationChange();

DisplayConfigurationChanges.m

#import "DisplayConfigurationChanges.h"

NSString * DisplayConfigurationWillChange = @"DisplayConfigurationWillChange";
NSString * DisplayConfigurationDidChange = @"DisplayConfigurationidChange";

void DisplayReconfigurationBlock (CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *userInfo) {
    if (flags & kCGDisplayBeginConfigurationFlag) {
        [[NSNotificationCenter defaultCenter] postNotificationName:DisplayConfigurationWillChange object:nil];
    } else {
        [[NSNotificationCenter defaultCenter] postNotificationName:DisplayConfigurationDidChange object:nil];
    }
}

void StartNotificationsOnDisplayConfigurationChange() {
    CGDisplayRegisterReconfigurationCallback(DisplayReconfigurationBlock, NULL);
}

void StopNotificationsOnDisplayConfigurationChange() {
    CGDisplayRemoveReconfigurationCallback(DisplayReconfigurationBlock, NULL);
}

Don't forget to import it in the -Bridging-Header.h

#import "DisplayConfigurationChanges.h"

Now we can start posting DisplayConfigurationWillChange and DisplayConfigurationDidChange notifications at any time and observe them from anywhere, including the Swift side of the app:

AppDelegate.swift

import Cocoa
import SpriteKit

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!

    func displayWillChange() {
        println("display will change \(NSScreen.mainScreen()!.frame.size)")
    }

    func applicationDidChangeScreenParameters(notification: NSNotification) {
        println("display did change \(NSScreen.mainScreen()!.frame.size)")
    }

    func applicationDidFinishLaunching(aNotification: NSNotification) {
        StartNotificationsOnDisplayConfigurationChange()
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "displayWillChange", name: DisplayConfigurationWillChange, object: nil)
    }

    func applicationWillTerminate(notification: NSNotification) {
        StopNotificationsOnDisplayConfigurationChange()
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}

Changing the display resolution in the System Preferences whilst the app is running (without any SKViews) would print:

display will change (1920.0, 1080.0)
display did change (1600.0, 900.0)

If we now add an SKView, the app would crash instead, printing only the first of the lines above. But this is success! This means we did get notified of the changes in good time to do something about it.

Milos
  • 2,728
  • 22
  • 24
  • It turns out that the only thing we could do "about it" is to relaunch the app! (If you're curious how to do that, see [Restarting OSX app programmatically](http://stackoverflow.com/a/29847800/1409907).) Thankfully, in our case, this will be of no consequence to the user, but is still very far from ideal. – Milos May 21 '15 at 11:38