sending a NSNotification between views - ios

This is my first attempt with NSNotification, tried several tutorials but somehow it's not working.
Basically I am sending a dictionary to class B which is popup subview (UIViewController) and testing to whether is has been received.
Could anyone please tell me what am I doing wrong?
Class A
- (IBAction)selectRoutine:(id)sender {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
NSDictionary *dictionary = [NSDictionary dictionaryWithObject:#"Right"
forKey:#"Orientation"];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"PassData"
object:nil
userInfo:dictionary];
createExercisePopupViewController* popupController = [storyboard instantiateViewControllerWithIdentifier:#"createExercisePopupView"];
//Tell the operating system the CreateRoutine view controller
//is becoming a child:
[self addChildViewController:popupController];
//add the target frame to self's view:
[self.view addSubview:popupController.view];
//Tell the operating system the view controller has moved:
[popupController didMoveToParentViewController:self];
}
Class B
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(receiveData:)
name:#"PassData"
object:nil];
}
- (void)receiveData:(NSNotification *)notification {
NSLog(#"Data received: %#", [[notification userInfo] valueForKey:#"Orientation"]);
}

If it hasn't registered to receive that notification yet - it will never receive it. Notifications don't persist. If there isn't a registered listener, the posted notification will be lost.

Specific to your problem, the receiver hasn't started observing before the notification is sent so the notification just gets lost.
More generally: What you're doing wrong is using notifications for this use case. It's fine if you're just playing around and experimenting but the kind of relationship you're modelling here is best actioned by retaining a reference to the view and calling methods on it directly. It's usually best if experimentation is realistic of the situation in which it would actually be used.
You should be aware of 3 basic communication mechanisms and when to use them:
Notifications
Use them to notify other unknown objects that something has happened. Use them when you don't know who wants to respond to the event. Use them when multiple different objects want to respond to the event.
Usually the observer is registered for most of their lifetime. It's important to ensure the observer removes itself from NSNotificationCenter before it is destroyed.
Delegation
Use delegation when one object wants to get data from an unknown source or pass responsibility for some decision to an unknown 'advisor'.
Methods
Use direct calls when you know who the destination object is, what they need and when they need it.

Related

Access View Controller's properties from AppDelegate

I currently have an Xcode project in which I am using Storyboards. In my AppDelegate, I need to set some properties that are contained in the .h files of other View Controllers in response to notifications that the app receives.
How do I instantiate a object of those view controllers in the AppDelegate so that I can access and modify their properties?
There are ways for the app delegate to get a handle to the right vc and communicate with it, but the better design is to have the information flow the other way around, letting the view controllers ask for information and update their own properties.
To do this, when the app delegate receives a notification, have it post a corresponding NSNotification (via NSNotificationCenter). The view controllers who care about the change can add themselves as observers for this notification and get the information. How can they get it? A few ways:
The textbook way is to have a model on the application, probably a singleton that has properties relevant to the view controllers. Idea two is to effectively make your app delegate a model by giving it properties that the vcs can interrogate. Last idea, the userInfo param on postNotificationName:(NSString *)notificationName object:(id)notificationSender userInfo:(NSDictionary *)userInfo can convey information to the observer.
EDIT - NSNotificationCenter is pretty easy to use. It goes like this:
In AppDelegate.m, when you get an external notification:
// say you want a view controller to change a label text and its
// view's background color
NSDictionary *info = #{ #"text": #"hello", #"color": [UIColor redColor] };
[[NSNotificationCenter defaultCenter] postNotificationName:#"HiEverybody" object:self userInfo:info];
In SomeViewController.m, subscribe to the message:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(observedHi:)
name:#"HiEverybody"
object:nil];
}
// unsubscribe when we go away
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// this method gets run when the notification is posted
// the notification's userInfo property contains the data that the app delegate provided
- (void)observedHi:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
self.myLabel.text = userInfo[#"text"];
self.view.backgroundColor = userInfo[#"color"];
}

Change UILabel in View Controller from Singleton

I am very new to iOS development and I'm struggling to make an app which connects to BLE devices. As I have many view controllers I need to keep the peripheral always connected in all of them.
To achieve this, I implemented all the BLE connection methods in a Singleton. This works just great, I call the connect method from View Controller and the Singleton connects to the peripheral.
Now, the problem is I have a UILabel in my view controller which I would like to update with the connection state (scanning, connecting, connected, disconnected) from the Singleton.
So I tried to get instance from the View Controller and change the label directly like:
MainViewController *controller = [[MainViewController alloc] init];
controller.myLabel.text = #"TEST";
I also instantiated the view controller class like:
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MyStoryboard" bundle: nil];
MainViewController *controller = (MainViewController*)[mainStoryboard instantiateViewControllerWithIdentifier:#"MainVC"];
Then I tried to create a method in the main View Controller:
- (void) updateLabel:(NSString *) labelText{
NSLog(#"CALLED IN MAIN");
self.myLabel.text = labelText;
}
And call it from Singleton like:
MainViewController *controller = [[MainViewController alloc] init];
[controller updateLabel:#"TEST"]
Which was called properly (NSLog was shown) but the label was not updated.
I don't really know how to update my View Controller label from the Singleton. Don't know neither if the way I'm trying to do it is the right one or not.
Any advice or help would be much appreciated. Thanks.
----- UPDATE: -----
Thanks to Mundi and Nikita, I got a better way to implement what I need through NSNotification. For all those who need it here is how I do it:
In my View Controller in viewDidLoad I call:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateConnectionLabel:) name:#"connectionLabelNotification" object:nil];
Then in the same class I implement the notification observer method like:
- (void)updateConnectionLabel:(NSNotification *) notification {
if ([[notification name] isEqualToString:#"connectionLabelNotification"]) {
self.connectionLabel.text = notification.object; //The object is a NSString
}
}
Then in my Singleton, when I need I call:
[[NSNotificationCenter defaultCenter] postNotificationName:#"connectionLabelNotification" object:[NSString stringWithFormat:#"CONNECTED"]];
When the View Controller receives the notification from the Singleton it updates the label with the text I add on the notification object (in this case #"CONNECTED").
You need to use NSNotification.
Here is sample code:
in viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mySelector:)
name:DeviceStateChanged
object:nil];
in dealloc:
[[NSNotificationCenter defaultCenter] removeObserver:self
name:DeviceStateChanged
object:nil];
also add a method in ViewController:
- (void) mySelector:(NSNotification *) notification {
// action performed
}
in Sigleton
- (void) foo {
/// some actions
// device connected
[[NSNotificationCenter defaultCenter] postNotificationName:DeviceStateChanged object:self];
///
}
Recommendation: move notification name to your constants and use constant name. For naming convention look at Apple guidelines
The proper way to do this is via NSNotification. This communication device is meant for exactly this kind of situation. It broadcast a message without caring whether the potential receiver is available.
In your view controllers, you call NSNotificationCenter's addObserver / removeObserver when they appear / disappear. You post the notification via postNotification:.

Why is my NSNotification its observer called multiple times?

Within an App I make use of several viewcontrollers. On one viewcontroller an observer is initialized as follows:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MyNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethod:) name:#"MyNotification" object:nil];
Even when removing the NSNotification before initializing the number of executions of myMethod: is being summed up by the amount of repeated views on the respective viewcontroller.
Why does this happen and how can I avoid myMethod: being called more then once.
Note: I made sure by using breakpoints that I did not made mistakes on calling postNotification multiple times.
Edit: This is how my postNotification looks like
NSArray * objects = [NSArray arrayWithObjects:[NSNumber numberWithInt:number],someText, nil];
NSArray * keys = [NSArray arrayWithObjects:#"Number",#"Text", nil];
NSDictionary * userInfo = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self userInfo:userInfo];
edit: even after moving my subscribing to viewwillappear: I get the same result. myMethod: is called multiple times. (number of times i reload the viewcontroller).
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MyNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethod:) name:#"MyNotification" object:nil];
}
edit: something seems wrong with my lifecycle. ViewDidUnload and dealloc are not getting called, however viewdiddisappear is getting called.
The way I push my Viewcontroller to the stack is as follows where parent is a tableview subclass (on clicking the row this viewcontroller is initiated:
detailScreen * screen = [[detailScreen alloc] initWithContentID:ID andFullContentArray:fullContentIndex andParent:parent];
[self.navigationController pushViewController:screen animated:YES];
Solution:
Moving removal of nsnotification to viewdiddisappear did the trick. Thanks for guidance!
Based on this description, a likely cause is that your viewcontrollers are over-retained and not released when you think they are. This is quite common even with ARC if things are over-retained. So, you think that you have only one instance of a given viewcontroller active, whereas you actually have several live instances, and they all listen to the notifications.
If I was in this situation, I would put a breakpoint in the viewcontroller’s dealloc method and make sure it is deallocated correctly, if that’s the intended design of your app.
In which methods did you register the observers?
Apple recommends that observers should be registered in viewWillAppear: and unregistered in viewWillDissapear:
Are you sure that you don't register the observer twice?
Ran into this issue in an application running swift. The application got the notification once when first launched. the notification increases the number of times you go into the background and come back. i.e
app launches one - add observer gets gets called once in view will appear or view did load - notification is called once
app goes into background and comes back, add observer gets called again in view will appear or view did load. notification gets called twice.
the number increases the number of times you go into background and come back.
code in view will disappear will make no difference as the view is still in the window stack and has not been removed from it.
solution:
observe application will resign active in your view controller:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "applicationWillResign:", name: UIApplicationWillResignActiveNotification, object: nil)
func applicationWillResign(notification : NSNotification) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
this will make sure that your view controller will remove the observer for the notification when the view goes into background.
it is quite possible you are subscribing to the notifications
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self userInfo:userInfo];
before self gets initialized. And trying to unsubscribe 'self' which isn't really subscribed to, and you will get all global myNotification notifications.
If your view was hooked up in IB, use -awakeFromNib: as the starting point to register for notifications
It is possible that the class with the observer is, quite appropriately, instantiated multiple times. When you are debugging it will kinda look like the notification is being posted multiple times. But if you inspect self you might see that each time is for a different instance.
This could easily be the case if your app uses a tab bar and the observer is in a base class of which your view controllers are subclasses.

iOS Communication between Model and ViewController

I am developing an app based on the Master-View template provided by Apple (it consists of two ViewControllers, the MasterViewController and the DetailViewController). I have added a Model for communication with my server.
However, when my Model receives a message from the server, it needs to call a method in the MasterViewController or DetailViewController class. How can I do this?
All help is greatly appreciated.
You could fire notifications from the model, which are handled by the Master and Detail View controllers.
In the model:
- (void)receivedMessageFromServer {
// Fire the notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReceivedData"
object:nil];
}
Handle the "ReceivedData" notification in your View Controller(s):
- (void)viewDidLoad {
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receivedDataNotification:)
name:#"ReceivedData"
object:nil];
}
- (void)receivedDataNotification:(id)object {
NSLog(#"Received Data!");
}
Actually the MVC pattern that Apple proposes allows for notifications from the model to the controller.
A good way to achieve this goal could be to deliver NSNotification objects through the NSNotificationCenter when your data changes, with information on what is changed, and let the listeners take care of it.
You should use the optional protocol delegate method. I have an answer with example how to setup delegate method in this PO.
Blocks are the way to go.
You need to have a reference to your model in ViewController. When you want to update data you send a message to the model and pass block as a parameter to it which would get called when the response is received from server.
For example:
View Controller
[self.model fetchDataFromRemoteWithCompletionHandler:^(id responseObject, NSError *error)
{
// responseObject is the Server Response
// error - Any Network error
}];
Model
-(void)fetchDataFromRemoteWithCompletionHandler:(void(^)(id, NSError*))onComplete
{
// Make Network Calls
// Process Response
// Return data back through block
onComplete(foobarResponse, error);
}

NSNotificationCenter and passing data between view controllers and my class/ Am I on the wrong way?

For test purposes I've created an application with UITabBar, 3 view controllers and my class DataAnalyzer (till now it analyses nothing:)).
TestAppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//.....
NSArray *allControllers = [self.tabBarController viewControllers];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
for (UIVideoEditorController * viewController in allControllers) {
[center addObserver:viewController selector:#selector(useUpdatedData:) name:#"dataUpdated" object:nil];
NSLog(#"%#", [viewController description]);
}
DataAnalizer *dataAnalizer = [[DataAnalizer alloc] init];
[center addObserver:dataAnalizer selector:#selector(useUpdatedData:) name:#"dataUpdated" object:nil];
// dataAnalizer can't be released here? Where should it be done?
return YES;
}
in 2-nd, 3-rd view controllers and DataAnalyzer class I added the same method
- (void) useUpdatedData:(NSNotification *)note {
NSLog(#"Notificatio received in *** view controller");
//do something with [note object] like show it on a label or store it to instance variable
}
in 1-st view controller I added text field to send a string as notification
- (void)textFieldDidEndEditing:(UITextField *)textField {
NSLog(#"First viewController textFieldDidEndEditing: value is %# ", textField.text);
NSNotificationCenter *note = [NSNotificationCenter defaultCenter];
[note postNotificationName:#"dataUpdated" object:textField.text];
}
When I send a string, I see NSLog message in console that notification is received, but can do nothing with the string like showing it on a label. I understand that it's because a view controllers load first time and they were not initiated. But why I'm getting NSLog message in console? Can I send a string to dataAnalizer class, do something there and then get a result to second and third view controllers?
Thank you in advance for your answers, because it seems all above is a wrong approach.
Apple documentation pretty clearly states that NSNotificationCenter does not retain it's observers, thats why you can't release the dataAnalyzer there - it would be dealloc'd and the notification would attempt to post to a nil reference.
I don't think it's a good idea to loop through an array of controllers and subscribe each one to a notification. The controller isn't guaranteed to be created at that point, and because it doesn't know it's been volunteered to answer a notification it also doesn't know to unsubscribe. Instead, subscribe to the notification(s) in the -init method of each view controller. That ensures the controller has been created and initialized, and makes each controller responsible for it's own actions.
I'm not entirely sure what your question is, could you rephrase it if the above didn't resolve your problem.
Also, be aware NSNotificationCenter will post to all observers, but it is not async - it waits for each one to finish processing the notification before sending to the next object.
You don't use correctly postNotification method. Your application data should be send using userinfo parameter with the method postNotificationName:object:userInfo:

Resources