Problems with NSNotificationCenter and UIPickerView - ios

I hope I have better luck with someone helping me on this one:
I have a UIPickerView where a user makes a selection and then presses a button. I can gladly obtain the users choice, as shown in my NSLog, and when this is done, I want to send a notification to another view controller that will show a label with the option selected. Well, although it seems everything is done right, somehow it does not work and the label stays intact. Here is the code:
Broadcaster:
if ([song isEqualToString:#"Something"] && [style isEqualToString:#"Other thing"])
{
NSLog (#"%#, %#", one, two);
[[NSNotificationCenter defaultCenter] postNotificationName:#"Test1" object:nil];
ReceiverViewController *receiver = [self.storyboard instantiateViewControllerWithIdentifier:#"Receiver"];
[self presentModalViewController:receiver animated:YES];
}
Observer:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification) name:#"Test1" object:nil];
}
return self;
}
-(void)receiveNotification:(NSNotification*)notification
{
if ([[notification name] isEqualToString:#"Test1"])
{
[label setText:#"Success!"];
NSLog (#"Successfully received the test notification!");
}
else
{
label.text = #"Whatever...";
}
}

I think you have a syntax error in your selector: #selector(receiveNotification). It should probably be #selector(receiveNotification:) with the colon since your method accepts the NSNotification *notification message. Without it, it's a different signature.

The issue is likely that the notification is sent (and therefore received) on a different thread than the main thread. Only on the main thread will you be able to update UI elements (like a label).
See my answer to this question for some insight into threads and NSNotifications.
Use something like:
NSLog(#"Code executing in Thread %#",[NSThread currentThread] );
to compare your main thread versus where your recieveNotifcation: method is being executed.
If it is the case that you are sending the notification out on a thread that is not the main thread, a solution may be to broadcast your nsnotifications out on the main thread like so:
//Call this to post a notification and are on a background thread
- (void) postmyNotification{
[self performSelectorOnMainThread:#selector(helperMethod:) withObject:Nil waitUntilDone:NO];
}
//Do not call this directly if you are running on a background thread.
- (void) helperMethod{
[[NSNotificationCenter defaultCenter] postNotificationName:#"SOMENAME" object:self];
}
If you only care about the label being updated on the main thread, you can perform that operation on the main thread using something similar to:
dispatch_sync(dispatch_get_main_queue(), ^(void){
[label setText:#"Success!"];
});
Hope that was helpful!

Related

Object parameter in method postNotification of NSNotificationCenter

In my iOS application, I am posting a NSNotification and catching it in one of my UIView in main thread. I want to pass extra information along with the notification. I was using userInfo dictionary of NSNotification for that.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotifyValueComputedFromJS" object:self userInfo:#{#"notificationKey":key,#"notificationValue":value,#"notificationColor":color,#"notificationTimeStamp":time}];
key, value, color and time are local variables which contains the value I need to pass. In UIView I am adding observer for this notification and I am using notification.userInfo to get these data
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"NotifyValueComputedFromJS" object:nil];
-(void)receiveNotification:(NSNotification *)notification
{
if ([notification.userInfo valueForKey:#"notificationKey"]!=nil && [[notification.userInfo valueForKey:#"notificationKey"] isEqualToString:self.notificationKey] && [notification.userInfo valueForKey:#"notificationValue"]!=nil) {
[self updateLabelWithValue:[notification.userInfo valueForKey:#"notificationValue"]];
}
}
The frequency in which this notification is posted is 4 times in one second. I am doing some animations also in main thread. The problem I am facing here is my UI is lagging. UI will respond to scroll events or touch events with huge delay(I have faced a delay of even 1 to 2 seconds). After some research I came to know that NSDictionary is bulky and will cause lag if used in main thread. Is there any other way I can pass my data through NSNotification?
I have tried out another way. I have created a custom NSObject class to save the data I want and I am passing it as the object parameter of postNotification method.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotifyValueComputedFromJS" object:customDataObject userInfo:nil];
Here customDataObject is an instance of my custom NSObject class. I know the parameter is meant to be the sender of notification(usually it will be self). Is it a wrong approach if I am sending a custom object as parameter?
As BobDave mentioned, the key is to send the notification on some thread other than the main UI thread. This can be accomplished with dispatch_async, or with a queue.
The typical pattern for this behavior is sender:
-(void)sendDataToObserver {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotifyValueComputedFromJS" object:customDataObject userInfo:userInfo:#{#"notificationKey":key,#"notificationValue":value,#"notificationColor":color,#"notificationTimeStamp":time}];
});
}
And receiver (NOTE: weak self because retain cycles):
-(void)addObserver {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"NotifyValueComputedFromJS" object:nil];
}
-(void)receiveNotification:(NSNotification *)notification {
if ([notification.userInfo valueForKey:#"notificationKey"]!=nil && [[notification.userInfo valueForKey:#"notificationKey"] isEqualToString:self.notificationKey] && [notification.userInfo valueForKey:#"notificationValue"]!=nil) {
__weak typeof (self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf updateLabelWithValue:[notification.userInfo valueForKey:#"notificationValue"]];
});
}
}
Maybe you could use - addObserverForName:object:queue:usingBlock:
and use a non-main queue to execute the block in order to reduce the lag. Also, shouldn't the observer be added in a UIViewController, not a UIView?

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

setText in UIlabel after notification call has no effect

i want update the text of my label every time i receive notification from nsmanageObjContext.
this is my code for add the observer:
- (IBAction)requestFotPhoto {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateLabel) name:NSManagedObjectContextDidSaveNotification
object:self.facebook.managedObjectContext];
and this is the method for update the label:
-(void)updateLabel
{
NSString *text = [NSString stringWithFormat:#"Downalad %i pictures",[Photo NumeberOfAllPhotosFromContext:self.facebook.managedObjectContext]];
dispatch_async(dispatch_get_main_queue(), ^{
//UIKIT method
NSLog(#"text %#",text);
[self.downlaodLabel setText:text];
});
}
i assume that updateLabel is execute in a another thread, so i execute the instructions for update the label on the main thread, but this code has no effect. where is the problem?
obviously the NSlog print the right message!
thanks!
In your situation you don't need to use dispatch_async, because notification handlers are run in the main thread. They are executed in a main loop on idle moments — sorry if I'm wrong with techincal words, english is not native for me.
And one more thing: you should't reference self from blocks, because self points to your block, and block points to self — they're not going to be released. If you really want to do it, you can read this question.
seems like:
your should move your NSNotificationCenter addObserver code, from your (IBAction)requestFotPhoto (seems is some button click event handler, which only run after user tapped) to viewDidLoad
shold like this:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateLabel) name:NSManagedObjectContextDidSaveNotification object:self.facebook.managedObjectContext];
}
and for noficacation handler, not use dispatch_async
should like this:
- (void)updateLabel:(NSNotification *) notification {
NSLog (#"updateLabel: notification=%#", notification);
if ([[notification name] isEqualToString: NSManagedObjectContextDidSaveNotification]) {
NSDictionary *passedInUserInfo = notification.userInfo;
NSString *yourText = [passedInUserInfo objectForKey:#"dataKey"];
//UIKIT method
NSLog(#"yourText=%#",yourText);
[self.downlaodLabel setText:yourText];
}
}
and somewhere else should send the text:
NSString *newText = #"someNewText";
NSDictionary *passedInfo = #{#"dataKey": newText};
[[NSNotificationCenter defaultCenter] postNotificationName:NSManagedObjectContextDidSaveNotification object: self userInfo:passedInfo];
for more detail pls refer another post answer

Dealing with two screens and one activity indicator in iOS

I have 3 screens on my app.First is login. Second is search and third is process the task.
On login i retrieve data from a web service. It returns data in XML format. So the data is considerably large. So i am doing that task on a background thread like this to stop Mainthread freezing up on me:
-(BOOL)loginEmp
{
.....some computation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
[self getAllCustomerValues];
});
}
-(void)getAllCustomerValues
{
....more computation.Bring the data,parse it and save it to CoreData DB.
//notification - EDIT
NSNotification *notification =[NSNotification notificationWithName:#"reloadRequest"
object:self];
[[NSNotificationCenter defaultCenter] postNotification : notification];
}
//EDIT
//SearchScreenVC.m
- (void)viewDidLoad
{
....some computation
[self.customerActIndicator startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(stopActivityIndicator)
name:#"reloadRequest"
object:nil];
}
- (void)stopActivityIndicator
{
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
So on condition that login was successful, i move to screen 2. But the background thread is still in process( i know because i have logs logging values) . I want an activity indicator showing up here (2nd screen)telling user to wait before he starts searching. So how do i do it?How can i make my activity indicator listen/wait for background thread. Please let me know if you need more info.Thanks
EDIT: so I edited accordingly but the notification never gets called. I put a notification at the end of getAllCustomerValues and in viewDidLoad of SearchScreen i used it. That notification on 2nd screen to stop animating never gets called. What is the mistake i am doing.?Thanks
EDIT 2: So it finally hits the method. I dont know what made it to hit that method. I put a break point. I wrote to stop animating but it wouldn't. I wrote hidesWhenStoppped and hidden both to YES. But it still keeps animating.How do i get it to stop?
Ok, if it is not the main thread, put the following in and that should fix it.
- (void)stopActivityIndicator
{
if(![NSThread isMainThread]){
[self performSelectorOnMainThread:#selector(stopActivityIndicator) withObject:nil waitUntilDone:NO];
return;
}
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
Could you put your background operation into a separate class and then set a delegate on it so you can alert the delegate once the operation has completed?
I havent tried this, its just an idea :)
You could use a delegate pointing to your view controller & a method in your view controller like:
- (void) updateProgress:(NSNumber*)percentageComplete {
}
And then in the background thread:
float percentComplete = 0.5; // for example
NSNumber *percentComplete = [NSNumber numberWithFloat:percentComplete];
[delegate performSelectorOnMainThread:#selector(updateProgress:) withObject:percentageComplete waitUntilDone:NO];

Slow response using NSNotification defaultCenter

Hi I am using NSNotificationCenter defaultCenter to implement the 'like' and 'comment' functions in my app.
//In Answer Table View
#implementation AnswerTableView
- (id)initWithParentController:(UIViewController *)pController andResourcePath:(NSString *)thisResourcePath {
....
// Notification to reload table when a comment is submitted
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reloadTable)
name:#"Comment Submitted"
object:nil];
// Notification to reload table when an answer is liked
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reloadTable)
name:#"Answer Liked"
object:nil];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
//In custom button implementation - THIS BUTTON IS CREATED IN EVERY CELL OF THE TABLEVIEW
#implementation UICustomButton
-(id)initWithButtonType:(NSString *)type {
self = [super init];
if (self) {
//Initialization done here
}
return self;
}
- (void)buttonPressed {
if ([btnType isEqualToString:#"like"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"Answer Liked" object:nil];
}
else if ([btnType isEqualToString:#"comment"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"Comment Submitted" object:nil];
}
}
However, I realize that after using these functions for a while, the response speed of the table reload gets slower and slower (to a point where it crashes).
Did I miss out anything in the implementation i.e. deallocating etc
You are repeatedly adding observers and the slowdown occurs because the notification code has to cycle over more and more observers to send notifications. You are probably crashing because you are leaking so many of these views.
Put a log statement in your dealloc to see if these instances are ever cleaned up. Also there can be timing issues with removeObserver in a dealloc method. try to remove the observer before dealloc if you can.
Sometimes its good to queue the event with Grand Central Dispatch to make sure its running on the main thread.
dispatch_async(dispatch_get_main_queue()

Resources