Test NSNotification delivery - nsnotificationcenter

I'm trying to make sure the NSNotification gets sent after reportIssue is called.
I get this error:
error: -[APHIssueComposerTests testPopulatedIssueIsReceived] : OCMockObject[APHIssueComposerTests]: expected method was not invoked: reportIssueNotificationReceived
In APHIssueComposer.m:
- (void) reportIssue {
APHIssue* issue = [self issue];
NSNotification* notification = [NSNotification notificationWithName:APHLogDataObjectNotification object:issue];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle];
[self discardIssue];
}
In APHIssueComposerTests.m:
- (void)setUp
{
[super setUp];
self.mockObserver = [OCMockObject mockForClass:[self class]];
[[NSNotificationCenter defaultCenter] addObserver:self.mockObserver
selector:#selector(reportIssueNotificationReceived)
name:APHLogDataObjectNotification
object:nil];
self.issueComposer = [[APHIssueComposer alloc] initWithTempDirectory:#"/my/fake/directory"];
}
- (void)testPopulatedIssueIsReceived
{
[[self.mockObserver expect] reportIssueNotificationReceived];
self.issueComposer.message = #"fake message.";
[self.issueComposer reportIssue];
[mockObserver verify];
[[NSNotificationCenter defaultCenter] removeObserver:mockObserver name:APHLogDataObjectNotification object:nil];
}
- (void)tearDown
{
[super tearDown];
[[NSNotificationCenter defaultCenter] removeObserver:mockObserver name:APHLogDataObjectNotification object:nil];
}
Why doesn't the mock object receive the notification?

The problem is that enqueueNotification is asynchronous.

Related

`UIAlertController` is not triggered in NSNotification response

This is my AlertView code :
- (void)initializeAlertControllerForOneButtonWithTitle:(NSString *)title withMessage:(NSString *)msg withYesButtonTitle:(NSString *)yesButtonTitle withYesButtonAction:(id)yesButtonAction
{
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:title
message:msg
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* yesBtn = [UIAlertAction
actionWithTitle:yesButtonTitle
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (self.activityIndicator.animating) {
[self.activityIndicator stopAnimating];
}
if ([title isEqualToString:#"Wrong Password!"]) {
self.editTextField.text = #"";
[self.editTextField becomeFirstResponder];
}
}];
[alert addAction:yesBtn];
[self presentViewController:alert animated:YES completion:nil];
}
I am trying to fire this alert in my NSNotificatoin Response method. My Notification Response code :
- (void)receiveSMSVerificationResponse:(NSNotification *)notification
{
SMSVerificationDigitClassModel *smsVerificationDigitClassModel = [[SMSVerificationDigitClassModel alloc] init];
smsVerificationDigitClassModel = [notification object];
if (smsVerificationDigitClassModel.viewControllerName == ViewControllerNameProfileInfoEditViewController) {
if ([self alreadyRegisteredPhoneNumber:smsVerificationDigitClassModel.phoneNumber] == YES) {
NSLog(#"jogajog");
[self initializeAlertControllerForOneButtonWithTitle:#"Already Registered!" withMessage:kAlreadyRegisteredPhoneNumberMSGForChangePhoneNumber withYesButtonTitle:#"Ok" withYesButtonAction:nil];
} else {
if ([AdditionalClasses internetConnectionCheck] == YES) {
self.userModelClass.phone_number = smsVerificationDigitClassModel.phoneNumber;
[self updateUserModel:self.userModelClass];
} else {
[self noInternetConnectionAlert];
}
}
//Check if that phone number is already used
// udate phone numner in server
// update phone number in core data
//[self goToSignUpViewControllerWithPhoneNumber:smsVerificationDigitClassModel.phoneNumber];
}
}
I check it from break point, this line [self initializeAlertControllerForOneButtonWithTitle:#"Already Registered!" withMessage:kAlreadyRegisteredPhoneNumberMSGForChangePhoneNumber withYesButtonTitle:#"Ok" withYesButtonAction:nil]; is actually calling, but the alert view is not popping up. It says:
"Warning: Attempt to present on whose view is not in the window hierarchy!"
I have tried to add notification observer methods :
- (void)addNotificationObserver
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveUserModelResponse:) name:#"sendUpdateRequestToServerForPhoneNumberWithUserModel" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveSMSVerificationResponse:) name:#"SMSVerificationForPhoneNumber" object:nil];
}
In viewDidLoad, viewDidAppear & in viewWillAppear and removeObserver in dealloc,
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"sendUpdateRequestToServerForPhoneNumberWithUserModel" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"SMSVerificationForPhoneNumber" object:nil];
}
but it is not showing at all. So, how do I change my window hierarchy! in this viewController. If you understand, please reply back. A lot of thanks in advance.
Call the initializeAlertControllerForOneButtonWithTitle method inside a main queue dispatch queue block.
All UI operation should be on main threat.
dispatch_async(dispatch_get_main_queue(), ^{
[self initializeAlertControllerForOneButtonWithTitle:#"Already Registered!" withMessage:kAlreadyRegisteredPhoneNumberMSGForChangePhoneNumber withYesButtonTitle:#"Ok" withYesButtonAction:nil];
});

NSNotificationCenter is not sending out notifications

I am developing a system to keep track of achievements and for that I use NSNotificationCenter. When an achievement is unlocked in a object in the app a notification is sent to JMAchievementHandler which sets a string to YES and saves the progress in NSUserDefaults. My problem is that the notifications are not working probably. Here is my code in JMAchievementHandler:
- (id)init {
self = [super init];
if (self) {
//Set strings to NO
achievementOne = #"NO";
achievementTwo = #"NO";
achievementThree = #"NO";
achievementFour = #"NO";
//Add observers
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationWithName:) name:#"achievement1" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationWithName) name:#"achievement2" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationWithName) name:#"achievement3" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationWithName) name:#"achievement4" object:nil];
//Add observer to observe delegate methods
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationFromDelegate:) name:#"notificationLaunch" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationFromDelegate:) name:#"notificationEnterBackground" object:nil];
}
return self;
}
- (void)receiveNotificationWithName:(NSNotification *)notification {
if ([[notification name] isEqualToString:#"achievement1"] || [achievementOne isEqualToString:#"NO"]) {
//unlock achievement
NSLog(#"%# is unlocked", [notification name]);
achievementOne = #"YES";
}
else if ([[notification name] isEqualToString:#"achievement2"] || [achievementTwo isEqualToString:#"NO"]) {
//unlock achievement
NSLog(#"%# is unlocked", [notification name]);
achievementTwo = #"YES";
}
else if ([[notification name] isEqualToString:#"achievement3"] || [achievementThree isEqualToString:#"NO"]) {
//unlock achievement
NSLog(#"%# is unlocked", [notification name]);
achievementThree = #"YES";
}
else if ([[notification name] isEqualToString:#"achievement4"] || [achievementFour isEqualToString:#"NO"]) {
//unlock achievement
NSLog(#"%# is unlocked", [notification name]);
achievementFour = #"YES";
}
}
- (void)receiveNotificationFromDelegate:(NSNotification *)notificationDelegate
{
if ([[notificationDelegate name] isEqualToString:#"notificationLaunch"]) {
[self loadDataOnLaunch];
}
else if ([[notificationDelegate name] isEqualToString:#"notificationEnterBackground"]) {
[self saveDataOnExit];
}
}
- (void)loadDataOnLaunch
{
NSLog(#"loadDataOnLaunch");
}
- (void)saveDataOnExit
{
NSLog(#"saveDataOnExit");
}
When I try to post a notification the NSLogs are not called. I use the following code to send notifications from my AppDelegate and ViewController.
- (void)achievement1ButtonPressed:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:#"achievement1" object:self userInfo:nil];
}
I hope you guys can help me out. Thanks a lot
Jonas
Do you even have a method named receiveNotificationWithName? When you entered the code, the autocomplete should have barfed on that, offering receiveNotificationWithName: instead (unless you wrote it first, without NSNotification*, then added it later...
Anyway, there is a big difference between the two. Also, your handler is buggy. You should revisit that code.
There are lots of reasons to prefer blocks, and this points to one of them. You can just put your code right in the registration, and not worry about giving the wrong selector.
[[NSNotificationCenter defaultCenter] addObserverForName:#"achievement1"
object:nil
queue:nil
usingBlock:^{
// Handle achievement1 right in this little block.
}];
These look like one-shot notifications, so if you want to unregister the notification handler after it runs one time, do this (note the __block).
__block id achievement1 = [[NSNotificationCenter defaultCenter]
addObserverForName:#"achievement1 ":^{
object:nil
queue:nil
usingBlock:^{
// Handle achievement1 right in this little block.
// Remove observer from notification center
[[NSNotificationCenter defaultCenter] removeObserver:achievement1];
}];
All of this looks like it should work (besides the colon, like Andrea mentioned). Is it possible that your button callback isn't being called for a similar reason? I would add an NSLog in your achievement1ButtonPressed: method to make sure.
What I can see is that only one of your addObesrver method call the right selector with the ":", thus the right method signature.

MPMoviePlayerViewController exits after background

When my app goes to background and back, modal view closes. Why is this happend? I tried with pause on and off. I have ARC enabled, if it is a useful info.
MPMoviePlayerViewController * player = [[MPMoviePlayerViewController alloc] initWithContentURL:targetURL];
[player.moviePlayer prepareToPlay];
[self presentMoviePlayerViewControllerAnimated:player];
In case anyone else stumbles across this (as I did before I found my own solution), I was able to prevent MPMoviePlayerViewController from exiting when the app backgrounds by subclassing it and removing it as an observer of UIApplicationDidEnterBackgroundNotification:
- (id)initWithContentURL:(NSURL *)contentURL {
self = [super initWithContentURL:contentURL];
if (self){
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}
return self;
}
You don't need to subclass MPMoviePlayerViewController - just do following after creating MPMoviePlayerViewController instance.
[[NSNotificationCenter defaultCenter] removeObserver:self.moviePlayerViewController name:UIApplicationDidEnterBackgroundNotification object:nil];
Here is full code that can help you
// Call this to start initialization and play movie
-(void)prepareMoviePlayer:(NSURL *)moviePath
{
moviePlayerViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:moviePath];
if ([[moviePlayerViewController moviePlayer] respondsToSelector:#selector(loadState)])
{
[[moviePlayerViewController moviePlayer] setControlStyle:MPMovieControlStyleNone];
[[moviePlayerViewController moviePlayer] setFullscreen:YES];
[[moviePlayerViewController moviePlayer] prepareToPlay];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moviePlayerLoadStateDidChange:)
name:MPMoviePlayerLoadStateDidChangeNotification
object:nil];
}
}
- (void) addPlayer
{
[[[moviePlayerViewController moviePlayer] view] setFrame:self.view.bounds];
[[self view] addSubview:[[moviePlayerViewController moviePlayer] view]];
}
static NSTimeInterval t;
// Call this on applicationWillResignActive
-(void) pauseMovieInBackGround
{
[[moviePlayerViewController moviePlayer] pause];
t = [[moviePlayerViewController moviePlayer] currentPlaybackTime];
[moviePlayerViewController.view removeFromSuperview];
}
// Call this on applicationWillEnterForeground
-(void) resumeMovieInFrontGround
{
[self addPlayer];
[[moviePlayerViewController moviePlayer] stop];
[[moviePlayerViewController moviePlayer] setInitialPlaybackTime:t];
[[moviePlayerViewController moviePlayer] play];
}
-(void)moviePlayerLoadStateDidChange:(NSNotification *)notification
{
if([[moviePlayerViewController moviePlayer] loadState] != MPMovieLoadStateUnknown)
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerLoadStateDidChangeNotification
object:nil];
[self addPlayer];
[[moviePlayerViewController moviePlayer] play];
}
}
-(void)videoPlayBackDidFinish:(NSNotification*)notification
{
[self dismissMoviePlayerViewControllerAnimated];
}
I am coming across this issue.
I've tried ChrisH's answer, but it didn't work for me until I put removeObserver into viewDidLoad() instead of init().
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}
Then it works. Enjoy it.

MPMoviePlayerController prepareToPlay not working with HTTP Live Streaming

I'm trying to play an HTTP live streaming video using a MPMoviePlayerController object, inside a modal view controller. If I run the code like this, everything works fine. I get a MPMoviePlaybackStatePlaying notification, and the video plays.
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerPlaybackStateDidChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:nil];
NSURL *url = [NSURL URLWithString:#"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"];
self.moviePlayerController = [[[MPMoviePlayerController alloc] initWithContentURL:url] autorelease];
self.moviePlayerController.view.frame = CGRectMake(0,0,320,416);
self.moviePlayerController.controlStyle = MPMovieControlStyleNone;
self.moviePlayerController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.moviePlayerController.view];
self.moviePlayerController.fullscreen = NO;
[self.moviePlayerController play];
}
I would prefer to use the prepareToPlay method, so that I can show a loading indicator, and for a nicer experience. However, I don't seem to be able to get this working. No MPMoviePlayerPlaybackStateDidChangeNotification ever gets called, and the stream doesn't play. The activity indicator just sits there spinning forever.
I've confirmed that prepareToPlay does get called, but nothing else seems to happen after that. I've also confirmed that this code works fine with a local movie file. It just seems to be with HTTP Live Streaming which fails. Can anyone see what I'm doing wrong?
- (void)viewDidLoad {
[super viewDidLoad];
[self.activityIndicator startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerPlaybackStateDidChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:nil];
NSURL *url = [NSURL URLWithString:#"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"];
self.moviePlayerController = [[[MPMoviePlayerController alloc] initWithContentURL:url] autorelease];
[self registerForMovieNotifications];
}
- (void)registerForMovieNotifications {
//movie is ready to play notifications
if ([self.moviePlayerController respondsToSelector:#selector(loadState)]) {
//this is a 3.2+ device
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayerLoadStateChanged:) name:MPMoviePlayerLoadStateDidChangeNotification object:nil];
self.moviePlayerController.movieSourceType = MPMovieSourceTypeStreaming;
[self.moviePlayerController prepareToPlay];
} else {
//pre-3.2 device
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePreloadDidFinish:) name:MPMoviePlayerContentPreloadDidFinishNotification object:nil];
}
//movie has finished notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(moviePlayBackDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
}
- (void) moviePlayerPlaybackStateDidChange:(NSNotification*)notification {
NSLog(#"playbackDidChanged");
MPMoviePlayerController *moviePlayer = notification.object;
MPMoviePlaybackState playbackState = moviePlayer.playbackState;
if(playbackState == MPMoviePlaybackStateStopped) {
NSLog(#"MPMoviePlaybackStateStopped");
} else if(playbackState == MPMoviePlaybackStatePlaying) {
NSLog(#"MPMoviePlaybackStatePlaying");
} else if(playbackState == MPMoviePlaybackStatePaused) {
NSLog(#"MPMoviePlaybackStatePaused");
} else if(playbackState == MPMoviePlaybackStateInterrupted) {
NSLog(#"MPMoviePlaybackStateInterrupted");
} else if(playbackState == MPMoviePlaybackStateSeekingForward) {
NSLog(#"MPMoviePlaybackStateSeekingForward");
} else if(playbackState == MPMoviePlaybackStateSeekingBackward) {
NSLog(#"MPMoviePlaybackStateSeekingBackward");
}
}
//3.2+ devices
- (void)moviePlayerLoadStateChanged:(NSNotification*)notification {
NSLog(#"load state changed");
//unless state is unknown, start playback
if ([self.moviePlayerController loadState] != MPMovieLoadStateUnknown) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerLoadStateDidChangeNotification object:nil];
self.moviePlayerController.view.frame = CGRectMake(0,0,320,416);
self.moviePlayerController.controlStyle = MPMovieControlStyleNone;
self.moviePlayerController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.moviePlayerController.view];
self.moviePlayerController.fullscreen = NO;
[self.moviePlayerController play];
}
}
//pre 3.2 devices
- (void) moviePreloadDidFinish:(NSNotification*)notification {
// Remove observer
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerContentPreloadDidFinishNotification object:nil];
[self.moviePlayerController play];
}
I don't know why, but I was able to solve this by moving the line self.moviePlayerController.controlStyle = MPMovieControlStyleNone; out of the moviePlayerLoadStateChanged: method, and into the viewDidLoad method.
Anyone have any ideas why this made a difference?

NSNotificationCenter, blocks and ivar of type SEL: can't get it to work

I'm trying the following:
Whenever an OAuth token changes, I want to update some of my view controllers. The view controllers I want to update all inherit from a view controller. This view controller will listen for notifications whenever a new access token is set or whenever the access token is cleared. Whenever the access token is set or cleared, I want to set a selector to a method that should be executed once the view controller will be displayed (i.e. on -viewWillAppear:).
Somehow the blocks inside the addObserverForName:object:queue:usingBlock: don't seem to get called. I can't get it to log even. Because of this the selectors never change. From what I've read using the __block attribute on an ivar should allow the ivar to be changed from within a block.
#interface SDViewController ()
- (void)reloadData;
- (void)clearData;
#end
#implementation SDViewController
{
__block SEL selectorOnViewWillAppear;
}
- (id)initWithDataSource:(id <SDViewControllerDataSource>)dataSource
{
self = [super init];
if (self)
{
selectorOnViewWillAppear = #selector(reloadData);
}
return self;
}
- (void)viewDidLoad
{
NSLog(#"view did load");
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserverForName:kAccessTokenChangedNotification object:self queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *note) {
NSLog(#"selector: test1");
selectorOnViewWillAppear = #selector(reloadData);
}];
[[NSNotificationCenter defaultCenter] addObserverForName:kAccessTokenClearedNotification object:self queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *note) {
NSLog(#"selector: test2");
selectorOnViewWillAppear = #selector(clearData);
}];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (selectorOnViewWillAppear)
[self performSelector:selectorOnViewWillAppear];
}
- (void)reloadData
{
NSLog(#"reloadData");
selectorOnViewWillAppear = nil;
}
- (void)clearData
{
NSLog(#"clearData");
selectorOnViewWillAppear = nil;
}
#end
Fixed it by changing the observer code from this:
[[NSNotificationCenter defaultCenter]
addObserverForName:kAccessTokenClearedNotification
object:self
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
NSLog(#"selector: test2");
selectorOnViewWillAppear = #selector(clearData);
}];
To this:
[[NSNotificationCenter defaultCenter]
addObserverForName:kAccessTokenClearedNotification
object:nil
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
NSLog(#"selector: test2");
selectorOnViewWillAppear = #selector(clearData);
}];
The Apple documentation related to this issue is found here. The object in the
-addObserverForName:object:queue:usingBlock: method should be the object that generates the notifications. Setting the object to nil fixed the issue.