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.
Related
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
I want to pass a NSString from one class to another class and add that NSString to an NSMutableArray in my second class. I'm believe i can use NSNotification for this, but i don't know how to pass an variable over notification. My code would something like this:
//class1.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property(strong,nonatomic)NSString *variableString;
#end
//class1.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize variableString = _variableString;
- (void)viewDidLoad
{
[super viewDidLoad];
[self setVariableString:#"test"];
[[NSNotificationCenter defaultCenter] postNotificationName: #"pasteString" object: _variableString];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
#end
//class2.h
#import <UIKit/UIKit.h>
#interface ViewController2 : UIViewController
#property(strong,nonatomic)NSMutableArray *arr;
#end
//class2.m
#import "ViewController2.h"
#interface ViewController2 ()
#end
#implementation ViewController2
#synthesize arr = _arr;
- (void)viewDidLoad:(BOOL)animated
{
[super viewDidLoad];
if(_arr == nil)
{
_arr = [[NSMutableArray alloc]init];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(incomingNotification:) name:#"pasteString" object:nil];
// Do any additional setup after loading the view.
}
- (void) incomingNotification:(NSNotification *)notification{
NSString *theString = [notification object];
[_arr addObject:theString];
}
#end
In sender class you can post a notification with an object with something like this:
[[NSNotificationCenter defaultCenter] postNotificationName: NOTIFICATION_NAME object: myString];
The listener or receiver class has to register for the notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(incomingNotification:) name:NOTIFICATION_NAME object:nil];
The method incomingNotification is:
- (void) incomingNotification:(NSNotification *)notification{
NSString *theString = [notification object];
...
}
EDIT
When you post the notification from "ViewController", is "ViewController2" loaded?
Send your Notification like this:
if(isComingFromHowToUseThisApp) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"noteFromVideoPlayer"
object:#"fromHowToUseThisApp"];
}else {
[[NSNotificationCenter defaultCenter] postNotificationName:#"noteFromVideoPlayer"
object:#"fromVideoPlayerViewiPad"];
}
Receive your notification like this:
// Reset the default value of table view in master split view
- (void)defaultCellSelectionOfTableView:(NSNotification *) objNotification {
// We need to change the default selection of row
NSString *noticeFromVideoPlayer = [objNotification object];
}// end defaultCellSelectionOfTableView
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.
Is it possible to use the class methods of some NSObject I've made to controls all existing instances?
I want a delegate class to be able to send messages to the Object's class methods which can then react appropriately with everything out there.
Sure. Just have all the objects register for notifications when they're created. For example:
- (id)init {
self = [super init];
if (!self) return nil;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(announceYourself:)
name:#"MYCLASS_ANNOUNCE"
object:nil];
return self;
}
+ (void)identifyInstances {
[[NSNotificationCenter defaultCenter] postNotificationName:#"MYCLASS_ANNOUNCE" object:self];
}
- (void)announceYourself:(id)notification {
NSLog(#"Instance %p reporting in!", self);
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self]; // VERY IMPORTANT -- IT WILL CRASH WITHOUT THIS
[super dealloc];
}
Not without using some collection of the objects. The simplest way to do it would be to store the objects in an array and send them all a message when you want something to happen. Specifically:
static NSMutableArray *allObjects = nil;
+ (void) initialize {
if (!allObjects) {
allObjects = [NSMutableArray new];
}
}
- (id) init {
if (self = [super init]) {
//Each object gets held in an array
[allObjects addObject:self];
}
return self;
}
+ (void) doSomethingToEveryObject {
[allObjects makeObjectsPerformSelector:#selector(doSomethingToThisObject)];
}
- (void) doSomethingToThisObject {
NSLog(#"Did something to %#",self];
}
- (void) release {
[super release];
//When the retain count reaches 1, then all external references have disappeared,
//and the only remaining reference is in the objects array.
if (self.retainCount == 1) {
[allObjects removeObject:self];
}
}
In the Follwoing ViewControllerClass I get EXC_BAD_ACCESS when trying to call presentModalViewController in ViewDidAppear method.
#import "SounderViewController.h"
#import "ASIFormDataRequest.h"
#import "ASIHTTPRequest.h"
#import "JSON.h"
#import "InfoViewController.h"
#implementation SounderViewController
#synthesize ipod;
#synthesize ivc;
#synthesize title_lb, artist_lb, check;
-(IBAction)showCurrentSongInfo{
MPMediaItem * song = [ipod nowPlayingItem];
NSString * title = [song valueForProperty:MPMediaItemPropertyTitle];
NSString * artist = [song valueForProperty:MPMediaItemPropertyArtist];
title_lb.text = title;
artist_lb.text = artist;
}
-(void)playbackStateChanged: (NSNotification*) notification{
[self showCurrentSongInfo];
NSLog(#"Playback state: %#",[notification name]);
if (ipod.playbackState != MPMusicPlaybackStatePlaying) {
NSLog(#"Is not playing");
[self presentModalViewController:self.ivc animated:YES];
}else if (ipod.playbackState == MPMusicPlaybackStatePlaying) {
NSLog(#"Is playing");
[self dismissModalViewControllerAnimated:YES];
}
}
-(void)nowPlayingItemChanged: (NSNotification*) notification{
[self showCurrentSongInfo];
NSLog(#"Playing item changed: %#",[notification name]);
}
- (void)viewDidLoad {
[super viewDidLoad];
self.ivc = [[InfoViewController alloc] initWithNibName:#"InfoViewController" bundle:nil];
self.ipod = [MPMusicPlayerController iPodMusicPlayer];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector (playbackStateChanged:)
name:#"MPMusicPlayerControllerPlaybackStateDidChangeNotification"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector (nowPlayingItemChanged:)
name:#"MPMusicPlayerControllerNowPlayingItemDidChangeNotification"
object:nil];
[[MPMusicPlayerController iPodMusicPlayer] beginGeneratingPlaybackNotifications];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if (ipod.playbackState != MPMusicPlaybackStatePlaying) {
[self presentModalViewController:self.ivc animated:YES];
}else{
[self showCurrentSongInfo];
}
}
-(IBAction)showInfoView{
[self presentModalViewController:self.ivc animated:YES];
}
#pragma mark View Methods
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
#end
Method call
[self presentModalViewController:self.ivc animated:YES];
in ViewDidAppear causes EXC_BAD_ACCESS.
I have tried debuging it with NSZombieEnabled but got only a stack call to main.
The thing that gets me crazy is that if the same code is run from method playbackStateChanged it works fine.
If any of you can help I wan't get bold as quick. Thanks.
I finally made it to work! But I think it's just quick fix.
So I found out that to make my ivc to show up I need to delay the call to presentModalViewController
[self performSelector:#selector(showWaitingMessageView:) withObject:self.ivc afterDelay:1];
That's it. It works.
I don't know why this helped, so if one of you gurus knows more about it please enlighten me.