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"];
}
Related
In my application has a view controller named "Home" with a textField.
I read about applicationDidEnterBackground and applicationWillTerminate methods in the AppDelegate file.
I know how to create, save, read data from a file.
My question is, How I can get an NSString from the "Home" viewController (that there store the textField data) to the AppDelegate applicationDidEnterBackground method and do there all my things with that data?
You could use NSNotificationCenter to register for a notification in your view controller that fires off whenever you enter applicationDidEnterBackground or applicationWillTerminate.
So in either of those methods you put something like
[[NSNotificationCenter defaultCenter] postNotificationName:#"someDescriptiveName" object:self userInfo:#{#"key" : #"value"}];
userInfo expects an NSDicitonary and you can pass it any type of object in there, in your case you dont need to pass anything from here back to your viewcontroller, your just using it as a means to let your view controller know the app is closing.
In your view controller you would register for that notification with something like this
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(methodToCall:) name:#"someDescriptiveName" object:nil];
Then whenever your appDelegate post that notification, your view controller which is registered to listen for it would fire off "methodToCall" which can be a method you right to do anything and it takes in an nsnotification which then lets you access the nsdicitonary its carrying.
- (void)methodToCall:(NSNotification *)notif{
NSLog(#"methodToCall fired with data %#",[[notif userInfo]valueForKey:#"key"]);}
You can do this with the help of this inside your controller:
-(id)init
{
if((self = [super init]))
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(appDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:[UIApplication sharedApplication]];
}
return self;
}
-(void)appDidEnterBackground:(NSNotification *)note {
NSLog(#"appDidEnterBackground");
}
you can also use applicationWillTerminate in place of UIApplicationDidEnterBackgroundNotification
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:.
App delegate:
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[NSNotificationCenter defaultCenter] postNotificationName:APP_REFRESH_NOTIFICATION object:nil];
}
In my view controller:
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doStuff) postNotificationName:APP_REFRESH_NOTIFICATION object:self];
}
- (void)doStuff never gets called. Why?
I assume that you've typed your question incorrectly and you'd meant to write addObserver:selector:name:object:, instead of addObserver:selector: postNotificationName:object: (such method doesn't exist).
In the documentation of - (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender
we can read:
notificationSender
The object whose notifications the observer wants
to receive; that is, only notifications sent by this sender are
delivered to the observer. If you pass nil, the notification center
doesn’t use a notification’s sender to decide whether to deliver it to
the observer.
So in your case, as you're passing object:nil in postNotificationName:object:, you also have to set object:nil in addObserver:selector:name:object:.
According to the documentation you also should replace the method doStuff with:
- (void)doStuff:(NSNotification *)notification
and use #selector(doStuff:) in addObserver:selector:name:object:.
You're passing self as the object parameter to addObserver:selector:name:object:, but doStuff doesn't accept any parameters, so the method call fails (silently). Your viewDidLoad should look like this:
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doStuff)
name:APP_REFRESH_NOTIFICATION
object:nil];
}
You're app delegate is posting a notification when the app becomes active, but your view controller isn't subscribing to that until its view gets loaded. If your app delegate is creating your view controller and loading it (which is probable) then your controller doesn't even exist at the time the notification is posted, which is why it isn't receiving it. If you use a storyboard, and that controller is the entry point in the storyboard, AND you use the info.plist for your app to set that storyboard as the main interface, then it will have already instantiated the controller and loaded its view by the time -applicationDidBecomeActive: is called, solving your problem.
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.
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: