Problems with NSNotificationCenter and loading the UIViewControlletr - ios

I have a UIViewController with button that brings another UIViewController. with clicking on button , as shown in my NSLog, and when this is done, I want to send a notification to load another viewcontroller . Well, although it seems everything is done right, somehow it does not work and the UIViewController not appear. Here is the code:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(infoPage:)
name:#"InfoPage" object:nil ];
-(void) infoPage:(NSNotification*)notification
{
NSLog(#"Code executing in Thread %#",[NSThread currentThread] );
InfoCtrol *i = [[InfoCtrol alloc] init];
i.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:i animated:YES];
}
my tabbaritem button
-(void)info {
[[NSNotificationCenter defaultCenter] postNotificationName:#"InfoPage"
object:nil
userInfo:nil];
NSLog(#"test not");
}
I think my problem is that: It's not in a mainThread but I do n't know how should I solved that:
I also used this but it didn't bring the UIViewController:
[self performSelectorOnMainThread:#selector(test) withObject:nil waitUntilDone:NO];
-(void)test{
[[NSNotificationCenter defaultCenter] postNotificationName:#"InfoPage"
object:nil
userInfo:nil];
}
If I just put this code in button, it displays the UIViewController, but I want to use NSNotificationCenter
InfoCtrol *i = [[InfoCtrol alloc] init];
i.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:i animated:YES];
My Log:
Code executing in Thread <NSThread: 0x1fd7c7e0>{name = (null), num = 1}
Update:
How should i remove last thread from mainThread

I don't know why you want to use a notification here, when you can perform the action directly without issue. But a simple thing you can do in notification methods that need to update UI is to just have them call themselves on the main thread if they're not already running on that thread:
-(void)myNotificationMethod:(NSNotification*)note {
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(myNotificationMethod:)
withObject:note
waitUntilDone:NO];
return;
}
// ... do some UI stuff
InfoCtrol *i = [[InfoCtrol alloc] init];
[self.navigationController pushViewController:i animated:YES];
}

Related

How to send & receive data using NSNotificationCenter in iOS (XCode6.4)

I am facing an issue with NSNotificationCenter.
I am not able to send message and receive message using NSNotificationCenter in latest ios 8.4 (XCode 6.4)
Please check the following code:
1) I want to send data using first view controller to another view.
so i have written the following code in first viewcontroller:
When user btn clicked method as following :
- (IBAction)btnClicked:(id)sender
{
[self postNotification];
[self performSegueWithIdentifier:#"asGo" sender:self];
}
-(void)postNotification{
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyNotification" object:self];
}
2) In Second view controller i have added observer in ViewWillApper as following :
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(eventListenerDidReceiveNotification:)
name:#"MyNotification"
object:nil];
}
-(void)eventListenerDidReceiveNotification:(NSNotification*)txt
{
NSLog(#"i got notfication:");
}
so eventListenerDidReceiveNotification is not called while come on view.
But i am not getting above log while i come on second vc with navigation
As others have noted, NSNotificationCenter doesn't work like a post office. It only delivers notifications if someone actually listens to them at the moment they arrived. This is the reason your eventListenerDidReceiveNotification method is not being called: you add an observer in viewWillAppear, which is called after the segue (I assume that you're using segues because of the performSegueWithIdentifier method in your code) is finished, so it's definitely called after postNotification has been called.
So, in order to pass data via NSNotificationCenter you have to add an observer before you post a notification.
The following code is completely useless and unnecessarily overcomplicated, you shouldn't do anything like that, but since you keep insisting on using a scheme like this, here you go:
//Didn't test this code. Didn't even compile it, to be honest, but it should be enough to get the idea.
NSString * const SOUselessNotificationName = #"MyUselessNotification";
#pragma mark - FIRST VC
#interface SOFirstVC : UIViewController
#end
#implementation SOFirstVC
NSString * const SOasGoSegueIdentifer = #"asGo";
- (IBAction)btnClicked:(id)sender {
[self performSegueWithIdentifier:SOasGoSegueIdentifer sender:self];
}
-(void)postNotification {
[[NSNotificationCenter defaultCenter] postNotificationName:SOUselessNotificationName object:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifer isEqualToString:SOasGoSegueIdentifer]) {
SOSecondVC *destinationVC = (SOSecondVC *)segue.destinationViewController;
[destinationVC registerToReceiveNotificationsFromObject:self];
[self postNotification];
}
}
#end
#pragma mark - SECOND VC
#interface SOSecondVC : UIViewController
-(void)registerToReceiveNotificationsFromObject:(id)object;
#end
#implementation SOSecondVC
-(void)registerToReceiveNotificationsFromObject:(id)object {
[[NSNotificationCenter defaultCenter] addObserver:self selector:(eventListenerDidReceiveUselessNotification:) name:SOUselessNotificationName object:object];
}
-(void)eventListenerDidReceiveUselessNotification:(NSNotification*)uselessNotification {
NSLog(#"I got a useless notfication! Yay!");
}
-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#end
NSNotificationCenter basically has 3 steps
Adding Observer like [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(open:) name:#"OpenDetail" object:nil];
Posting Notification [[NSNotificationCenter defaultCenter] postNotificationName:#"OpenDetail" object:self];
Removing Observer [[NSNotificationCenter defaultCenter] removeObserver:self name:#"OpenDetail" object:nil];
I think you are posting your notification and then later adding observer while it's vie versa. You have to add observer first then post notification.
HTH
First you have to setup the data you want to send
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:myObject forKey:#"aKey"];
Then you post it with the data like so:
[[NSNotificationCenter defaultCenter] postNotificationName: #"MyNotification" object:nil userInfo:userInfo];
And finally you read the data off the notification:
-(void)eventListenerDidReceiveNotification:(NSNotification*)notification
{
NSLog(#"i got notification:");
NSDictionary *userInfo = notification.userInfo;
NSString *myObject = [userInfo objectForKey:#"aKey"];
}

Can someone explain this strange behaviour in iOS (central dispatch + notifications + UI refresh)?

Brief : I have a queue that sends notifications. A view controller subscribes to them and when it receives them it displays an image.
Problem : the first time, everything goes well. When I come back later to the view, I see the log that the notification was received but the image is not displayed.
Note : the background thread is a queue : dispatch_queue_create("scan", DISPATCH_QUEUE_SCAN) and from this queue the notification are posted.
#property(strong, nonatomic) id observer;
- (void)viewDidAppear:(BOOL) animated {
[super viewDidAppear:animated];
self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:MY_NOTIF object:nil queue:nil usingBlock:^(NSNotification *note) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"notification");
[self.img setImage:[UIImage imageNamed:#"register-ok"]];
[self.img setNeedsDisplay]; // useless
});
}
}
- (void)viewDidDisappear:(BOOL) animated {
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:observer name:MY_NOTIF object:nil];
}
This is really getting me crazy. Thanks in advance for your help
EDIT : changes done :
NSLog(#"viewDidAppear. createObserver. self: %#");
[[NSNotificationCenter defaultCenter] addObserverForName:MY_NOTIF object:nil queue:nil usingBlock:^(NSNotification *note) {
NSLog(#"will call redraww. self: %#");
[self performSelectorOnMainThread:#selector(redraww:) withObject:nil waitUntilDone:YES];
}
-(void) redraww:(NSObject*)input {
NSLog(#"redraww self : %#", self);
[self.img setImage:[UIImage imageNamed:#"register-ok"]];
}
-(void) onRegistrationFinish {
NSLog(#"remove observer. self %#", self);
[[NSNotificationCenter defaultCenter] removeObserver:self name:(NSString *)NOTIF_SOLE_UUID object:nil];
}
console log
viewDidAppear. createObserver self: <RegisterLeftViewController: 0x15e34550>
will call redraww. self:<RegisterLeftViewController: 0x15e34550>
redrawww self : <RegisterLeftViewController: 0x15e34550>
remove observer. self <RegisterLeftViewController: 0x15e34550>
************************* Second time
viewDidAppear. createObserver self: <RegisterLeftViewController: 0x15e98ba0>
will call redraww. self:<RegisterLeftViewController: 0x15e34550>
redrawww self : <RegisterLeftViewController: 0x15e34550>
remove observer. self <RegisterLeftViewController: 0x15e98ba0>
Seems like you're doing a few things that could cause trouble.
The trouble could be coming from a number of places. Here's some things that may fix the problem:
Move observer registration to viewDidLoad
Move observer de-registration to dealloc
use __block __weak id observer to ensure self is properly captured in your notification handler. See iOS NSNotificationCenter Observer not being removed
Hope that helps

AVCamPreviewView initialization is slow and sometimes causes crash after closing / reopening view - using AVCam example project

I'm using the AVCam example project, the latest version from the start of this month (Feb 2014). I have added flash selection functionality and removed the recording feature but I don't think that has any relevance to the issue.
When changing view, and reopening the AVCam view a number of times in succession the app either crashes, or it takes a long time for the preview view to initialize (~15 seconds). This only occurs sometimes.
I assumed the issue relates to clean up when changing view, but the example has what looks to be thorough clean up:
- (void)viewDidDisappear:(BOOL)animated
{
dispatch_async([self sessionQueue], ^{
[[self session] stopRunning];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:[[self videoDeviceInput] device]];
[[NSNotificationCenter defaultCenter] removeObserver:[self runtimeErrorHandlingObserver]];
[self removeObserver:self forKeyPath:#"sessionRunningAndDeviceAuthorized" context:SessionRunningAndDeviceAuthorizedContext];
[self removeObserver:self forKeyPath:#"stillImageOutput.capturingStillImage" context:CapturingStillImageContext];
[self removeObserver:self forKeyPath:#"movieFileOutput.recording" context:RecordingContext];
});
}
Here is my code for changing the view (sending captured image in at the same time) (I'm loading the view using a storyboard modal action):
-(void)dealWithNewImage:(UIImage*)imageIn {
[self saveCamState];
//change view and send us the image
UIStoryboard *storyboard = self.storyboard;
DrawingController *drawView = (DrawingController *)[storyboard instantiateViewControllerWithIdentifier:#"DrawingView"];
drawView.imageIn = imageIn;
[self presentViewController:drawView animated:NO completion:nil];
}
I have no idea what is causing the crash and why sometimes the camera preview takes ~15 seconds to display.
Thanks in advance for any help!
Adding the following, within the viewDidDisappear method, after [[self session] stopRunning]; fixed the issue for me:
for(AVCaptureInput *input in captureSession.inputs) {
[captureSession removeInput:input];
}
for(AVCaptureOutput *output in captureSession.outputs) {
[captureSession removeOutput:output];
}

View doesn't appear if displayed in response to a notification but does otherwise

I have a view I want to display on a certain event. My view controller is listening for a broadcast notification sent by the model and it attempts to display the view when it receives the broadcast.
However the view is not appearing. BUT if I run the exact same view code from elsewhere within the View Controller then it will be displayed. Here's some code from the VC to illustrate:
- (void) displayRequestDialog
{
MyView *view = (MyView*)[[[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil] objectAtIndex:0];
view.backgroundColor = [UIColor lightGrayColor];
view.center = self.view.window.center;
view.alpha = 1.0;
[self.view addSubview:view];
}
- (void) requestReceived: (NSNotification*) notification
{
[self displayRequestDialog];
}
When the above code is run the view does not appear. However if I add the call to displayRequestDialog elsewhere, for example to viewDidAppear:
- (void) viewDidAppear
{
[self displayRequestDialog];
}
Then it is displayed.
My question therefore obviously is why can I get the view to successfully appear if I call displayRequestDialog from viewDidLoad, but it will not display if called from within requestReceived?
(Note that I am not calling requestReceived prematurely before the view controller / its view has loaded and displayed)
At first I was posting the notification like this:
[[NSNotificationCenter defaultCenter] postNotificationName: kMyRequestReceived
object: self
userInfo: dictionary];
Then I tried this:
NSNotification *notification = [NSNotification notificationWithName:kMyRequestReceived object:self userInfo:dictionary];
NSNotificationQueue *queue = [NSNotificationQueue defaultQueue];
[queue enqueueNotification:notification postingStyle:NSPostWhenIdle];
Then I tried this:
dispatch_async(dispatch_get_main_queue(),^{
[[NSNotificationCenter defaultCenter] postNotificationName: kMyRequestReceived
object: self
userInfo: dictionary];
});
Then I tried this:
[self performSelectorOnMainThread:#selector(postNotificationOnMainThread:) withObject:dictionary waitUntilDone:NO];
- (void) postNotificationOnMainThread: (NSDictionary*) dict
{
[[NSNotificationCenter defaultCenter] postNotificationName: kMyRequestReceived
object: self
userInfo: dict];
}
And I have tried invoking displayRequestDialog like this:
dispatch_async(dispatch_get_main_queue(),^{
[self displayRequestDialog];
});
I have found the cause of the view not displaying - the frame's origin is getting negative values when invoked via the notification code but positive values when invoked otherwise and thus was being displayed off the screen.
No idea why there should be a difference however.
You are not listening for the notification. Do so like this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(displayRequestDialog) name:kMyRequestReceived object:nil];
As far as we cannot see the code you use to register your controller to receive notifications I would recommend you to use the observer registration method which enforce getting notifications on the main thread "for free"
[[NSNotificationCenter defaultCenter] addObserverForName:#"Notification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
NSLog(#"Handle notification on the main thread");
}];

removeObserver not working

I have next code:
#implementation SplashViewVC
- (void)viewDidLoad
{
[super viewDidLoad];
self.splashView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"Default.png"]];
self.activityIndicator.originY = 355.f;
[[NSNotificationCenter defaultCenter] addObserverForName:NCDownloadComplete object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *n){
NSInteger errorCode = [n.userInfo[#"errorCode"] integerValue];
[self.activityIndicator stopAnimating];
if (errorCode == ERROR_CODE_NO_CONNECTION) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Some problem with server" delegate:self cancelButtonTitle:#"try again" otherButtonTitles:nil];
[alertView show];
} else if (errorCode == 0) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}];
[self downloadData];
}
- (void)downloadData
{
[self.activityIndicator startAnimating];
[[Server sharedServer] getMovieData];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
[self downloadData];
}
- (void)viewDidDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super viewDidDisappear:animated];
}
#end
So I put breakpoints in begin of viewDidLoad method, in viewDidDisappear. When I launch app that first go to viewDidload, after downloading it is go to viewDidDisappear.
But during my app I again download data and post notification: NSDownloadComplete. And in this VC it is work, but I removed later using:
[[NSNotificationCenter defaultCenter] removeObserver:self]
This VC use viewDidLoad once in the beginning & can not again addObserver.
What is wrong?
EDIT
I try put addObserver method to viewWillAppear or viewWillDisappear - no results.
I add NSLog(#"addObserver"); before
[[NSNotificationCenter defaultCenter] addObserverForName...
in viewDidLoad
and write
- (void)viewDidDisappear:(BOOL)animated
{
NSLog(#"removeObserver");
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super viewDidDisappear:animated];
}
In log I see:
2013-06-10 14:32:05.646 myApp[9390:c07] addObserver
2013-06-10 14:32:06.780 myApp[9390:c07] removeObserver
What wrong?
EDIT 2
you can see that observer must be removed but it again run block in addObserver method
Apart from add/remove observer calls not properly being balanced, at noted in the other answers, there is another problem.
Your code to remove the observer is wrong. For a block-based observer, the return value of addObserver must be given as argument to removeObserver. So you should add a property
#property(nonatomic, strong) id observer;
to the class. Then you add the observer with
self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:NCDownloadComplete object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *n){
// ...
}];
and remove it with
[[NSNotificationCenter defaultCenter] removeObserver:self.observer];
What e1985 is trying to expose is that your addObserver and removeObserver calls are not properly balanced. viewDidLoad is called only once after the VC initialization, but viewDidDisappear is called each time the view controller is moved off screen.
To resolve your issue you must balance your addObserver and removeObserver calls, either by making them in viewDidLoad and the other in dealloc, or - as e1985 suggested - in viewDidAppear: and viewDidDisappear:.
EDIT: Ok, so your problem comes from the fact that you are using addObserverForName:object:queue:usingBlock: which do not register self as observer (as addObserver:selector:name:object: would do if you pass self as first argument).
So in your case, [[NSNotificationCenter defaultCenter] removeObserver:self]; does nothing because self is not an observer. You should instead call removeObserver: on the return value of addObserverForName:object:queue:usingBlock:, as shown in the doc:
Return Value
An opaque object to act as the observer.
So your code should looks something like:
// header file .h
#interface SplashViewVC : UIViewController
#property (strong, nonatomic) id downloadCompleteObserver;
#end
// implementation file .m
#implementation SplashViewVC
- (void)viewDidLoad
{
[super viewDidLoad];
// [...] snip
self.downloadCompleteObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NCDownloadComplete object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *n){
NSInteger errorCode = [n.userInfo[#"errorCode"] integerValue];
[self.activityIndicator stopAnimating];
if (errorCode == ERROR_CODE_NO_CONNECTION) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Some problem with server" delegate:self cancelButtonTitle:#"try again" otherButtonTitles:nil];
[alertView show];
} else if (errorCode == 0) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}];
[self downloadData];
}
// [...] snip
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self.downloadCompleteObserver];
[super dealloc];
}
#end
The pattern you are using is not correct. You should add the observer in viewDidAppear: and remove it in viewDidDisappear:.

Resources