Search code examples
macossprite-kitnsnotificationcenter

NSNotificationCenter with SpriteKit


I am using NSNotificationCenter to notify when keys are pressed on the keyboard. When moving between scenes, if an additional key is pressed too quickly after the key that causes the scene transition is pressed the app crashes. I'm not sure if it's the previous scene that no longer receives notifications or if the next scene's observer for notifications is not set up. What can I do to stop this from happening? Here's the code for two different scenes and the custom view that handles the notifications. Essentially, I'm posting a notification for key presses in CustomSKView and then I handle the presses in the respective scenes in a method called keyPressed: that is not listed here.

LevelSelectScene.m

@implementation LevelSelectScene

-(void)didMoveToView:(SKView *)view {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyPressed:) name:@"KeyPressedNotificationKey" object:nil];

    //perform scene setup here
    ...
}

-(void)willMoveFromView:(SKView *)view {
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:@"KeyPressedNotificationKey"
                                              object:nil];

    //perform additional cleanup before moving to next scene
    ...

}

Menu.m

-(void) didMoveToView:(SKView *)view {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyPressed:) name:@"KeyPressedNotificationKey" object:nil];

    //perform menu setup here
    ...
}


-(void) willMoveFromView:(SKView *)view {
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:@"KeyPressedNotificationKey"
                                              object:nil];

    //perform additional cleanup before moving to next scene
    ...
}

CustomSKView.m

#import "CustomSKView.h"

@implementation CustomSKView:SKView {

}

- (id) initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    return self;
}

- (void) keyDown:(NSEvent *)theEvent {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"KeyPressedNotificationKey"
                                                        object:nil
                                                      userInfo:@{@"keyCode" : @(theEvent.keyCode)}];
}

@end

EDIT: Stack Trace

2015-08-15 05:47:08.199 PianoKeyboardTest[21854:4643404] -[NSPathStore2 keyPressed:]: unrecognized selector sent to instance 0x10050d110
2015-08-15 05:47:08.199 PianoKeyboardTest[21854:4643404] -[NSPathStore2 keyPressed:]: unrecognized selector sent to instance 0x10050d110
2015-08-15 05:47:08.200 PianoKeyboardTest[21854:4643404] (
0   CoreFoundation                      0x00007fff8575803c __exceptionPreprocess + 172
1   libobjc.A.dylib                     0x00007fff9227376e objc_exception_throw + 43
2   CoreFoundation                      0x00007fff8575b0ad -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3   CoreFoundation                      0x00007fff856a0e24 ___forwarding___ + 1028
4   CoreFoundation                      0x00007fff856a0998 _CF_forwarding_prep_0 + 120
5   CoreFoundation                      0x00007fff8571445c __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
6   CoreFoundation                      0x00007fff85604634 _CFXNotificationPost + 3140
7   Foundation                          0x00007fff83e8e9d1 -[NSNotificationCenter postNotificationName:object:userInfo:] + 66
8   PianoKeyboardTest                   0x000000010001d50e -[CustomSKView keyDown:] + 270
9   AppKit                              0x00007fff8ba1c11b -[NSWindow _reallySendEvent:isDelayedEvent:] + 5452
10  AppKit                              0x00007fff8b3add76 -[NSWindow sendEvent:] + 470
11  AppKit                              0x00007fff8b3aa9b1 -[NSApplication sendEvent:] + 4199
12  AppKit                              0x00007fff8b2d3c68 -[NSApplication run] + 711
13  AppKit                              0x00007fff8b250354 NSApplicationMain + 1832
14  PianoKeyboardTest                   0x0000000100005322 main + 34
15  libdyld.dylib                       0x00007fff8f1ee5c9 start + 1
)

EDIT: Solution

Here are the changes that I made to CustomSKView.

#import "CustomSKView.h"

@implementation CustomSKView:SKView {
    // Add instance variables here

}

- (id) initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
        // Allocate and initialize your instance variables here

    }
    return self;
}

- (void) keyDown:(NSEvent *)theEvent {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"KeyPressedNotificationKey"
                                                        object:nil
                                                      userInfo:@{@"keyCode" : @(theEvent.keyCode)}];
}

//overridden version of SKScene's presentScene: transition: method
-(void) presentScene:(SKScene *)scene transition:(SKTransition *)transition {
    [[NSNotificationCenter defaultCenter] removeObserver:self.scene
                                                    name:@"KeyPressedNotificationKey"
                                                  object:nil];
    [super presentScene:scene transition:transition];
}

@end

Solution

  • To remove an observer from the current SKScene whenever the game transitions to a new scene, override the presentScene method in your custom view class, remove the observer, and then call the super class's presentScene:

    - (void) presentScene:(SKScene *)scene transition:(SKTransition *)transition {
        [[NSNotificationCenter defaultCenter] removeObserver:self.scene
                                                        name:@"KeyPressedNotificationKey"
                                                      object:nil];
        [super presentScene:scene transition:transition];
     }