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);
}
Related
I have an app that is connected to a device by Bluetooth.
I want the app to send a command that indicates that the app is going to close in the app delegate method : (void)applicationWillTerminate:(UIApplication *)application {
One word: NSNotificationCenter
I'm not sure what you need to set the data to because you can't pass the data seamlessly via NSNotificationCenter; however you were going to figure that out in your UIApplicationDelegate anyway, so why can't you do it in the view controller directly.
In your case there is no need to do anything in your application delegate, because this notification allows your view controller to act as a mini app delegate (in that you can get termination status and so on).
Therefore...
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(TXdata:) name:UIApplicationWillTerminateNotification object:nil];
}
- (void)TXdata:(NSString *) data {
NSString *newData = data;
if (newData == nil) {
newData = ... // Figure out what your data should be here.
}
//do whatever with your data here.
}
I quote:
UIApplicationWillTerminateNotification
Posted when the app is about to terminate.
This notification is associated with the delegate applicationWillTerminate: method. This notification does not contain a userInfo dictionary.
You should create a class separate from the view controller and app delegate to handle the BLE communication. That way, the view controller and the app delegate can both have access and provide a better "separation of concerns" for your app. This new class might work well as a singleton.
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"];
}
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.
Last week I asked this question: Refresh the entire iOS app
#Jim advised me to use the notification center. I was not able to figure out how to work with notifications and I was told to ask another question, I tried for the full week to figure it out on my own, so here goes.
I have a view with multiple subviews. One of the subviews is a search bar (not the tableview one, just a custom text box), the user can search for a new person here and the entire app will be updated screen by screen.
When the user taps on the GO button in the search subview I make the call to the server to get all the data. After which I post this notification:
[self makeServerCalls];
[[NSNotificationCenter defaultCenter] postNotificationName:#"New data" object:Nil];
Now in the init of my parent view controller I have a listener
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(viewDidLoad) name:#"New data" object:nil];
I know this is most probably wrong, so can anyone explain to me how to use notifications properly in my situation? Or if there is a better way of doing what I want.
Thanks for any help you can give me.
When you post a notification, it will cause all register observers to be notified. They get notified by having a message sent to them... the one identified by the selector. As mentioned in the comments, you should not use viewDidLoad. Consider this...
- (void)newDataNotification:(NSNotification *notification) {
// Do whatever you want to do when new data has arrived.
}
In some early code (viewDidLoad is a good candidate):
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(newDataNotification:)
name:#"New data"
object:nil];
That's a terrible name, BTW. Oh well. This registration says that your self object will be sent the message newDataNotification: with a NSNotification object whenever a notification is posted with the name "New data" from any object. If you want to limit which object you want to receive the message from, provide a non-nil value.
Now, when you send the notification, you can do so simply, like you did in your code:
[[NSNotificationCenter defaultCenter] postNotificationName:#"New data" object:nil];
and that will make sure (for practical purposes) that [self newDataNotification:notification] is called. Now, you can send data along with the notification as well. So, let's say that the new data is represented by newDataObject. Since you accept notifications from any object, you could:
[[NSNotificationCenter defaultCenter]
postNotificationName:#"New data"
object:newDataObject];
and then in your handler:
- (void)newDataNotification:(NSNotification *notification) {
// Do whatever you want to do when new data has arrived.
// The new data is stored in the notification object
NewData *newDataObject = notification.object;
}
Or, you could pass the data in the user info dictionary.
[[NSNotificationCenter defaultCenter]
postNotificationName:#"New data"
object:nil
userInfo:#{
someKey : someData,
anotherKey : moreData,
}];
Then, your handler would look like this...
- (void)newDataNotification:(NSNotification *notification) {
// Do whatever you want to do when new data has arrived.
// The new data is stored in the notification user info
NSDictionary *newData = notification.userInfo;
}
Of course, you could do the same thing with the block API, which I prefer.
Anyway, note that you must remove your observer. If you have a viewDidUnload you should put it in there. In addition, make sure it goes in the dealloc as well:
- (void)dealloc {
// This will remove this object from all notifications
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
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: