I'm using SpriteKit in Objective C for Mac OS. I'm a noob when it comes to working with NSNotification, but I used the following method in another app I was working on, but it's now not working. Perhaps there's a better method, but I want to be able to capture key presses based on what state the app is in.
The Main Issue
The state begins in GameBeginState. Once the spacebar is pressed, it moves into GamePlayState. If I press any other keys, it shows that it's using the keyPressed: method from GameBeginState as opposed to the keyPressed method in GamePlayState. Any ideas why? I can't figure this out for the life of me.
Details
I created a custom view called CustomNotificationHandler and set my main SKView in the project.
Here's the code for CustomNotificationHandler:
CustomNotificationHandler
#import "CustomNotificationHandler.h"
#implementation CustomNotificationHandler: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)}];
}
#end
In my main class GameScene I have a GKStateMachine set up that initializes 3 states. The classes for the states are below:
GamePlayState.m
#import "GameReadyState.h"
#import "GamePlayState.h"
#interface GameReadyState()
#property (weak) GameScene *scene;
#end
#implementation GameReadyState
-(instancetype) initWithScene:(GameScene*)scene {
if (self = [super init]) {
self.scene = scene;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyPressed:) name:#"KeyPressedNotificationKey" object:nil];
}
return self;
}
-(void) didEnterWithPreviousState:(GKState *)previousState {
}
-(void) willExitWithNextState:(GKState *)nextState {
}
-(BOOL) isValidNextState:(Class)stateClass {
return stateClass == [GamePlayState class];
}
-(void)keyPressed:(NSNotification*)notification {
NSNumber *keyCodeObject = notification.userInfo[#"keyCode"];
NSInteger keyCode = keyCodeObject.integerValue;
NSLog(#"GAME READY key = %ld", (long)keyCode);
switch (keyCode) {
case 49:
[self.stateMachine enterState:[GamePlayState class]];
break;
default:
break;
}
}
#end
Here's the 2nd state...pretty much the same as the 1st:
GamePlayState
#import "GamePlayState.h"
#import "GamePauseState.h"
#interface GamePlayState()
#property (weak) GameScene *scene;
#end
#implementation GamePlayState
-(instancetype) initWithScene:(GameScene*)scene {
if (self = [super init]) {
self.scene = scene;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyPressed:) name:#"KeyPressedNotificationKey" object:nil];
}
return self;
}
-(void) didEnterWithPreviousState:(GKState *)previousState {
[_scene setPaused:NO];
}
-(BOOL) isValidNextState:(Class)stateClass {
return stateClass == [GamePauseState class];
}
-(void) updateWithDeltaTime:(NSTimeInterval)seconds {
[_scene.sequence moveLeft];
}
-(void)keyPressed:(NSNotification*)notification {
NSNumber *keyCodeObject = notification.userInfo[#"keyCode"];
NSInteger keyCode = keyCodeObject.integerValue;
NSLog(#"GAME PLAY key = %ld", (long)keyCode);
switch (keyCode) {
case 53:
[self.stateMachine enterState:[GamePauseState class]];
[_scene setPaused:YES];
break;
default:
break;
}
}
#end
Related
I have such a helper class.
#interface CustomOnScreenshot : NSObject;
#property (copy) void (^finishedCallback)(id sender);
-(instancetype)initWithCallback: (void (^)(id sender))callback;
+(instancetype)onScreenshot:(void (^)(id sender))callback;
#end
#implementation CustomOnScreenshot
-(instancetype)initWithCallback: (void (^)(id sender))callback{
self = [super init];
if (self) {
self.finishedCallback = callback;
[self subscribeEvent];
}
return self;
}
+(instancetype)onScreenshot:(void (^)(id sender))callback{
CustomOnScreenshot * onScreenShot = [self new];
[onScreenShot setFinishedCallback:callback];
return onScreenShot;
}
-(void)subscribeEvent{
NSLog(#"CustomOnScreenshot subscribeEvent");
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doOnScreenShot:) name:UIApplicationUserDidTakeScreenshotNotification object:nil];
}
-(void)unsubscribeEvent{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationUserDidTakeScreenshotNotification object:nil];
}
-(void)doOnScreenShot: (id)sender{
if (self.finishedCallback) {
self.finishedCallback(sender);
}
}
-(void)dealloc{
NSLog(#"CustomOnScreenshot dealloc");
[self unsubscribeEvent];
}
The problem is that if you use it as intended, then the object is immediately destroyed
- (void)viewDidLoad {
[super viewDidLoad];
[CustomOnScreenshot onScreenshot:^(id sender) {
// CUSTOM code
}];
}
Log:
CustomOnScreenshot subscribeEvent
CustomOnScreenshot dealloc
All works only when I use the result in property, but I find this excessive
#property (strong, nonatomic) CustomOnScreenshot * customOnScreenshot;
- (void)viewDidLoad {
[super viewDidLoad];
self.customOnScreenshot = [CustomOnScreenshot onScreenshot:^(id sender) {
// CUSTOM code
}];
}
If you don't have a strong reference to the CustomOnScreenshot instance, then the object will be deallocated by ARC as as soon as +onScreenshot: finishes execution.
That's why the #property fixes it.
If you don't want an #property, then I'd suggest a singleton.
I currently developing a small dictionary app under OSX for my own use, I would like to have a feature that when I hit the return key, the focus would go to the nssearchfeild.
So I try to make the app to receive keyDown event using a NSView and NSViewController told by this tutorial.
But every time I start the app, it wouldn't receive the keyDown event. I have to click on the window once, then hit the keyboard, so that it can receive keyDown event.
What did I do wrong? Can anyone help me out with this problem? I have been stuck in this problem for days, and searching throught Google and API wouldn't help much.
Thanks in advance!
Here is my code for AppDelegate.m
#import "AppDelegate.h"
#import "MyDictViewController.h"
#interface AppDelegate()
#property (nonatomic,strong) IBOutlet MyDictViewController *viewController;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.viewController = [[MyDictViewController alloc] initWithNibName:#"MyDictViewController" bundle:nil];
[self.window.contentView addSubview:self.viewController.view];
self.viewController.view.frame = ((NSView*)self.window.contentView).bounds;
[self.window makeKeyAndOrderFront:nil];
}
#end
And My ViewController.m
#import "MyDictViewController.h"
#import "FileHelper.h"
#import <Carbon/Carbon.h>
#interface MyDictViewController ()
#property (weak) IBOutlet NSTableView *wordsFilteredTable;
#end
#implementation MyDictViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.IMAGE_FILE = [NSImage imageNamed:#"Document.png"];
self.wordlist = [FileHelper readLines];
self.filterWordlist = [[NSMutableArray alloc] init];
}
return self;
}
- (void)loadView
{
[super loadView];
[self.view becomeFirstResponder];
}
-(void)keyDown:(NSEvent*)theEvent
{
NSLog(#"Caught key event");
}
-(void)keyUp:(NSEvent *)theEvent
{
unsigned short keycode = [theEvent keyCode];
switch (keycode)
{
case kVK_Return:
[self.searchField becomeFirstResponder];
default:
break;
}
}
-(void)mouseDown:(NSEvent*)theEvent
{
NSLog(#"Caught mouse event");
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
self.wordsFilteredTable.rowHeight = 37;
NSTableCellView *cellView = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
if( [tableColumn.identifier isEqualToString:#"WordColumn"] )
{
NSString *word = [self.filterWordlist objectAtIndex:row];
cellView.textField.stringValue = word;
cellView.imageView.image = self.IMAGE_FILE;
return cellView;
}
return cellView;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [self.filterWordlist count];
}
- (void)controlTextDidChange:(NSNotification *)obj
{
NSTextView* textView = [[obj userInfo] objectForKey:#"NSFieldEditor"];
self.currentWord = [textView string];
[self.filterWordlist removeAllObjects];
for(NSString* word in self.wordlist) {
if ([word hasPrefix:self.currentWord]) {
[self.filterWordlist addObject:word];
}
}
[self.wordsFilteredTable reloadData];
}
#end
And my AppView.m
#import "AppView.h"
#implementation AppView
- (void)setViewController:(NSViewController *)newController
{
if (viewController)
{
[super setNextResponder:[viewController nextResponder]];
[viewController setNextResponder:nil];
}
viewController = newController;
if (newController)
{
[super setNextResponder: viewController];
[viewController setNextResponder:[self nextResponder]];
}
}
- (void)setNextResponder:(NSResponder *)newNextResponder
{
if (viewController)
{
[viewController setNextResponder:newNextResponder];
return;
}
[super setNextResponder:newNextResponder];
}
#end
I want to implement iAd in my viewController so that i created the singleton class for iAd here the code that i used but it doesnt display iAd in my viewController.
adWhirlSingleton.h
#import <Foundation/Foundation.h>
#import "iAd/ADBannerView.h"
#interface adWhirlSingleton : NSObject <ADBannerViewDelegate> {
ADBannerView *adView;
UIViewController *displayVC;
}
#property (strong, nonatomic) ADBannerView *adView;
#property (strong, nonatomic) UIViewController *displayVC;
+(id)sharedAdSingleton;
-(void)adjustAdSize:(CGFloat)x:(CGFloat)y;
#end
adWhirlSingleton.m
#import "adWhirlSingleton.h"
#implementation adWhirlSingleton
static adWhirlSingleton* _sharedAdSingleton = nil;
#synthesize adView, displayVC;
+(id)sharedAdSingleton
{
#synchronized(self)
{
if(!_sharedAdSingleton)
_sharedAdSingleton = [[self alloc] init];
return _sharedAdSingleton;
}
return nil;
}
+(id)alloc
{
#synchronized([adWhirlSingleton class])
{
NSAssert(_sharedAdSingleton == nil, #"Attempted to allocate a second instance of a singleton.");
_sharedAdSingleton = [super alloc];
return _sharedAdSingleton;
}
return nil;
}
-(id)init
{
self = [super init];
if (self != nil) {
// initialize stuff here
self.adView.delegate=self;
}
return self;
}
-(void)dealloc
{
displayVC = nil;
if (adView) {
[adView removeFromSuperview]; //Remove ad view from superview
[adView setDelegate:nil];
adView = nil;
}
[super dealloc];
}
-(void)adjustAdSize:(CGFloat)x :(CGFloat)y
{
[UIView beginAnimations:#"AdResize" context:nil];
[UIView setAnimationDuration:0.7];
adView.frame = CGRectMake(x, y, 320, 50);
[UIView commitAnimations];
}
-(BOOL)adWhirlTestMode
{
return YES;
}
-(NSString *)adWhirlApplicationKey
{
return #"xxxxxxxxxxxxx";
}
-(UIViewController *)viewControllerForPresentingModalView
{
return displayVC;
}
-(void)bannerViewDidLoadAd:(ADBannerView *)banner {
[self adjustAdSize:0 :410];
}
#end
myViewController.m
#import "adWhirlSingleton.h"
-(void)viewWillAppear:(BOOL)animated {
adWhirlSingleton *adWhirlSingle = [adWhirlSingleton sharedAdSingleton];
adWhirlSingle.displayVC = self;
[adWhirlSingle adjustAdSize:0 :self.view.frame.size.height -50];
[self.view addSubview:adWhirlSingle.adView];
[self.view bringSubviewToFront:adWhirlSingle.adView];
NSLog(#"Ad Banner View");
}
This is how i implemented the singleton class for iAd when i excute this i didnt get iAd displayed over my ViewController.
If anyone know how to implement this please help me to get out of this issue. Thanks in Advance.
For example, a UITextField cannot be its own delegate, but is it OK to just have it register itself as an observer of its own notifications? Looks weird but seems to work fine. Thoughts?
// MyTextField.h
#interface MyTextField : UITextField
#end
// MyTextField.m
#interface MyTextField ()
- (void)myTextFieldDidChange:(NSNotification *)notification;
#end
#implementation MyTextField
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(myTextFieldDidChange:)
name:UITextFieldTextDidChangeNotification
object:self];
}
}
- (void)myTextFieldDidChange:(NSNotification *)notification {
// Do custom stuff here.
}
#end
What you're doing seems fine, but there's a purer solution for this particular example:
// MyTextField.h
#interface MyTextField : UITextField
#end
// MyTextField.m
#interface MyTextField ()
- (void)myTextFieldDidChange:(UITextField *)textField;
#end
#implementation MyTextField
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self addTarget:self action:#selector(myTextFieldDidChange:)
forControlEvents:UIControlEventEditingChanged];
}
return self;
}
- (void)myTextFieldDidChange:(MyTextField *)myTextField {
// Do custom stuff here.
}
#end
Check out the UIControlEvents reference.
I'm sure this is really obvious to someone, but this simple thing is really frustrating me.
I have a class I made called Class_Sprite, which is a sub-class of CCSprite.
I have a method in this class that is supposed to both create the texture for any given instance of Class_Sprite, and then move it to (200,200).
The program runs in the sim but all I get is a black screen.
I was able to render the sprite directly from the layer class.
Here are the files.
Class_Sprite:
#import "Class_Sprite.h"
#implementation Class_Sprite
-(id)init
{
if ((self = [super init]))
{
}
return self;
}
-(void)make:(id)sender
{
sender = [Class_Sprite spriteWithFile:#"Icon.png"];
[sender setPosition: ccp(200, 200)];
}
#end
Class Sprite header:
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface Class_Sprite : CCSprite {
}
-(void)make:(id)sender;
#end
HelloWorldLayer.m (where the method is being called)
#implementation HelloWorldLayer
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
// on "init" you need to initialize your instance
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super" return value
if( (self = [super init])) {
Class_Sprite *pc = [[Class_Sprite alloc] init];
[pc make:self]; //here is where I call the "make" method
[self addChild:pc];
[pc release];
}
return self;
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
// in case you have something to dealloc, do it in this method
// in this particular example nothing needs to be released.
// cocos2d will automatically release all the children (Label)
// don't forget to call "super dealloc"
[super dealloc];
}
#end
And finally the header file for HelloWorldLayer
#import "cocos2d.h"
#import "Class_Sprite.h"
// HelloWorldLayer
#interface HelloWorldLayer : CCLayer
{
}
// returns a CCScene that contains the HelloWorldLayer as the only child
+(CCScene *) scene;
#end
Thanks for your time
Try changing to this in Class_Sprite.m:
#implementation Class_Sprite
-(id)init
{
if ((self = [super initWithFile:#"Icon.png"]))
{
}
return self;
}
-(void)make:(CCNode *)sender
{
[self setPosition: ccp(200, 200)];
[sender addChild:self];
}
#end
And use it in HelloWorldLayer as follows:
Class_Sprite *pc = [[Class_Sprite alloc] init];
[pc make:self];
[pc release];