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 SKView
s) 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.