I have a function called sendDataToMotor. It is in my First View Controller classes. I have another view controller called SecondViewController. I need to call this function from the Second View Controller.m class. I tried declaring the property:
#property(nonatomic,assign)UIViewController* firstController;
in my SecondViewController.h class. Furthermore, I wrote the code bellow in the viewDidLoad part of my SecondViewController.m class (where I want the function to be called).
secondViewController = [[SecondViewController alloc] initWithNibName:#"secondViewController" bundle:nil];
secondViewController.firstController = self;
[self.firstController performSelector:#selector(sendDataToMotor)];
But, Im getting an error with the first word in that code (secondViewController) because of an undeclared identifier issue. Furthermore, I get an error with the second line (secondViewController.firstController = self) because secondViewController has an unknown name type.
In summary, I don't care if you use the above code to answer my question: that was just something I tried to implement that I found online. However, I'm looking for the simplest way to call a function from another View Controller.
Notification Center could be solution to you question.
Receiver UIViewController
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveNotification:)
name:#"myNotification"
object:nil];
}
- (void)receiveNotification:(NSNotification *)notification
{
if ([[notification name] isEqualToString:#"myNotification"]) {
//doSomething here.
}
}
Sender UIViewController
- (void)sendNotification {
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self];
}
You want to use the delegate pattern, and you are almost there.
This line in secondVC:
#property(nonatomic,assign)UIViewController* firstController;
should be generalised so as not refer to a specific type
#property(nonatomic,weak)id <delegateProtocol> delegate;
And you should accompany that with a protocol declaration in the header of your secondVC (above the #interface declaration), something like
#protocol SecondVCDelegate
- (void) sendDataToMotor;
#end
In firstVC interface you can declare your adherence to the delegate protocol in the first line of the #interface declaration in the header file
#interface firstVC < SecondVCDelegate >
Or in the first line of a private interface declaration in the .m file
#interface firstVC() < SecondVCDelegate >
Then you won't need to use performSelector (which anyway should be preceded by a safety check) as the compiler will alert you of errors.
In firstVC after creating secondVC, set it's delegate property to self(i.e. firstVC)
secondVC.delegate = self;
then in secondVC you can just call the method directly on it's delegate:
[self.delegate sendDataToMotor];
I explain this in more (wordy) detail here...
https://stackoverflow.com/a/14910469/1375695
There are many problems in your code. I'm going to assume the second chunk of code you included is actually in -viewDidLoad in FirstViewController not the second.
The error you're getting is because you aren't putting the type before secondViewController. It should be SecondViewController *secondViewController = ...
This probably still won't work for you because when you perform your transition to the second view controller, you won't be using the same object.
Related
Description of the problem is shown below:
Function contained in the UIViewController link functions contained in the NSObject
NSObject return value to another function contained in UIViewController
When a function referenced to UIViewController it does not update UILabel
UILabel found in UIViewController
NSObject class:
-(void)getCategoryId:(NSString *)categoryid {
categoryMap *catMap = [[categoryMap alloc] init];
[catMap getCategoryId:categoryid];
catMap.nePOI = categoryid;
}
UIViewController:
-(void)getCategoryId:(NSString *)categoryid {
self.label.text = categoryid;
}
You are creating an new categoryMap View controler object and you are updating its label, not the one from the categoryMap view controller that is being currently displayed.
To achieve what you want, the "NSObject" instance would have to have a refen-rence (property) pointing to the ViewController that is actually displayed on screen.
-(void)getCategoryId:(NSString *)categoryid {
// View controller is created
categoryMap *catMap = [[categoryMap alloc] init];
// label updated
[catMap getCategoryId:categoryid];
catMap.nePOI = categoryid;
// End of the method, View controller is destroyed
// See the problem here??? You are not updated the good viewController...
}
Although this is not a recommended way to go. Your viewController should ask the object what to display, not the other way around. But, in your case, you can fix this with a notification. Replace the implementation of getCategoryId: in the object class by
[[NSNotificationCenter defaultCenter] postNotificationWithName:#"categorySelected" object:categoryId];
and subscribe to it in your view controler viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(getCategoryId:) name:#"categorySelected" object:nil];
and then replace the getCategoryId: method of your view controller by:
-(void)getCategoryId:(NSNotification *)notif {
self.label.text = (NSString *)(notif.object);
}
That should do... ;)
To better understand architecture behind the ViewController principles, please refer to Apple MVC documentation: https://developer.apple.com/library/mac/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html
This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 7 years ago.
I am struggling on calling method from viewController B to viewController A. Need to call viewController B close button click to Dismissviewcontroller then immediately need to call one method and want to pass two string values on viewController A. Its like reverse process.
FYI : I am using Storyboard and present viewController for B. A is the mainviewcontroller.
use viewWillAppear in controller A.
Post a notification from controller B and add
observer on controller A.
Post notification on controller B close button
[[NSNotificationCenter defaultCenter] postNotificationName:"NAME" object:nil userInfo:nil];
Add observer on controller A:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(perform:) name:#"NAME" object:nil];
Implement delegates on
controller B and implement it on controller A so once you click
on close button controller B just call the delegate and perform what ever you want.
Implement KVO
well the easiest way but not the most efficient is to make a global object of the viewController A and viewController A view did load method
call that global variable and make it equal to self and
in then the dismiss from viewController B
will use
[self dismissViewControllerAnimated:YES completion:^{
// here you can create a code for calling the global **viewController A** object to call the function you need
}];
conclusion :
in viewController A header file :
extern A* AGlobalInstance;
and in A.m file just below the #import "A.h"
A* AGlobalInstance;
and in the viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
AGlobalInstance = self;
}
then in B.m button just use
[self dismissViewControllerAnimated:YES completion:^{
[AGlobalInstance function];
}];
but you must go to A viewController first before going to B to make it work
As the above answers suggested you to use Delegates and Notification so I am not gonna suggest you those. Apart from them I would like to ask you to go for Block.
typedef void(^valueHandler)(id anyObject1, id anyObject2);
#viewController A
B *instanceOfB;
[instanceOfB setValueHandlerBlock^(id anyObject1, id anyObject2) {
// Here you can receive the values.
}];
#viewController B
#property (nonatomic, copy) valueHandler valueHandlerBlock;
//To pass the value
if (valueHandlerBlock) {
valueHandlerBlock(#"a String value", anArray);
// When ever the above line will execute it will pass the values to view controller A.
}
There are many option for passing values
Use protocol and delegates
Use unwind segues
Use Notifications
there are many other option to do the same google on above points you will get tons of demos for this.
Trying to get a better understand of iOS delegation. I'm using UIImagePickerController as a reference but what's a good code example to use a delegate to dismiss my view controller?
I have a TabBarViewController that calls AViewController and want to use delegation to dismiss AViewController.
In this case, the idea is that the class that presented the new viewcontroller, should also be the one that dismisses it. The AViewController may have no clue how it was presented, so it wants to let the presenter, the TabBarViewController, handle the dismissal in whatever form needed. So, we need to define a protocol, say AViewControllerProtocol, which allows there to be a standard definition of the dismissal call:
This goes in AViewControllerProtocol.h:
#protocol AViewControllerProtocol <NSObject>
#required
- (void) dismissWithDone:(UIViewController *)viewController;
- (void) dismissWithCancel:(UIViewController *) viewController;
#optional
- (void)dismissWithDone:(UIViewController *)viewController andPlaySoundFile:(NSString *)soundPath;
#end
This goes in a file called AViewControllerProtocol.h. Both TabBarViewController.m and AViewController.m will import it. Think of this as a pact that any user of AViewController must agree to before it can utilize it. Similarly, you can't use UITableView without making a pact to observe the UITableViewDelegate and UITableViewDataSource protocols (those are two separate protocols).
Any class which wants to present AViewController, can see from the protocol definition that AViewController expects two required methods in order to be properly dismissed. It also has an optional third method that requests the presenter to play a sound after dismissing.
In order for AViewController to do its piece of this pact, it needs to get and store one piece of information about who the presenter is. That is called the delegate. The delegate is a property defined in the #interface of AViewController, in AViewController.h:
This goes in AViewController.h:
#property (nonatomic, weak) id<AViewControllerProtocol> delegate;
Now, the presenter, TabBarViewController, needs to do its bit. It needs to define the two required methods, plus maybe the optimal one, and it also needs to set the delegate value:
In TabBarViewController.m, in the #implementation:
This goes in TabBarViewController.m:
- (void) dismissWithDone:(UIViewController *)viewController
{
[self saveData:viewController.dataToSave]; // this could be the results that need to be saved
[viewController dismissViewControllerAnimated:YES completion:^{
;
}];
}
- (void) dismissWithCancel:(UIViewController *)viewController
{
// don't save data
[viewController dismissViewControllerAnimated:YES completion:^{
;
}];
}
The delegate value is set where AViewController is first created and/or before it is presented:
This also goes in TabBarViewController.m:
AViewController * aVC = [AViewController.alloc init];
aVC.delegate = self;
aVC.data = ...; // this may be the data you want changed by the VC
[self presentViewController:aVC animated:YES completion:^{
}];
Setting the delegate here is the only connection that the AViewController class has with its presenting viewController - which is the whole point here: child classes really shouldn't have to know a whole lot about the classes that utilize them.
Lastly, the AViewController class needs to add the following in order to call, via the delegate, back to the presenting class - so in AViewController.m:
This goes in AViewController.m:
-(IBAction)userHitButton:(id)sender
{
if (sender == doneButton) {
if ([_delegate respondsToSelector:#selector(dismissWithDone:)]) {
[_delegate dismissWithDone:self];
}
} else {
if ([_delegate respondsToSelector:#selector(dismissWithCancel:)]) {
[_delegate dismissWithCancel:self];
}
}
}
If you are wondering why the calling class has to:
aVC.delegate = self;
It is because there are situations where the class that actually creates a child class, isn't the one that will handle the delegate calls. In that case, instead of self, you put an instance of a class that will handle the delegate callbacks. For example, lets say that you have AViewController, BViewController and CViewController. They all get data from the user that needs to be saved. A class by the imaginary name of ABCDataHandler could be the one that can handle the dismissWithDone: and dismissWithCancel: callbacks and saves the data as needed, and TabBarViewController can stay out of any data handling activity.
That's to put it as simply as I can. I hope I haven't made any typos here :-)
I'm trying to segue an object from a table cell to a UITabBarController. My code to send the segue to a view controller is this:
#import "DetailViewController.h"
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"UpdateContacts"]) {
NSManagedObject *selectedDevice = [self.contactarray objectAtIndex:[[self.tableView indexPathForSelectedRow] row]];
DetailViewController *destViewController = segue.destinationViewController;
destViewController.contactdb = selectedDevice;
refreshControl = nil;
}
}
Also in DetailsViewController.h I´m adding the contactdb property
this one is working ok, now i don't know how to segue it to a UITabBarController so i can access it from my 2 view controllers that i have in the UITabBarController.
I'm not clear, is DetailViewController your TabBarController you are referring to?
Generally speaking, when you want to pass data between view controllers where the flow is a little more complex than normal, you can use a notification/listener pattern. For example, in the passing controller you would do something like this:
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_REFRESH
object:contactDb];
In your receiving controller, you would register to listen to the notification like this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(gotData:)
name:NOTIFICATION_REFRESH
object:nil];
And in that same controller, here is the code to capture that notification and object:
- (void)gotData:(NSNotification *) notification {
NSObject *myData = [notification object];
}
You will need to define the notification types in a header like this:
extern NSString* const NOTIFICATION_REFRESH;
And it's implementation in the .m file like this:
NSString* const NOTIFICATION_REFRESH = #"notificationRefresh";
For that kind of purpose I usually keep a singleton object that is visible in any place of project (makin #import in .pch file). Then I declare all necessary propeties which I want to share between different objects (e.g. controllers) and use them by assigning values and accesing. For instance, in prepareForSegue I would assign contactdb property declared in that common singleton object and then get the value from other controller.
I have the following setup in my app:
A UITabbarController with 3 viewcontrollers, with embeded UINavigationControllers.
The 3 viewcontrollers inheret/superclass from a UIViewController subclass called "SVC", in order to implement elements which is used in all of the 3. viewcontrollers and prevent duplicated code. In this "SVC" class I have setup a delegate called "dismissDelegate" (which is used to tell when the tabbarcontroller is dimissed).
#protocol ModalViewDelegate <NSObject>
- (void)didDismissModalViewFrom:(UIViewController *)viewController;
#end
#property (weak, nonatomic) id <ModalViewDelegate> dismissDelegate;
My other viewcontroller which segues to the UITabbarController, implements this delegate in order to get information about, when the tabbarcontroller is dismissed.
the SVC class notifies the delegate of dismissal of the tabbar like so:
[self.dismissDelegate didDismissModalViewFrom:self];
I now want to set the delegate of all the viewcontrollers which inherts from the SVC class (all the tabbar viewcontrollers) to this viewcontroller and I try to do this via the prepareToSegue method like so:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"ToTab"]) {
UITabBarController *tabBarController = segue.destinationViewController;
for (UINavigationController *navController in tabBarController.viewControllers) {
for (UIViewController *vc in navController.viewControllers) {
_SubclassVC = (SVC *) vc.superclass;
_SubclassVC.dismissDelegate = self;
}
}
}
}
But I get the following error:
+[SVC setDismissDelegate:]: unrecognized selector sent to class 0xbca68
My questions:
Is this the right way to tackle this senario (get information about dismissal of a viewcontroller and setup this delegate in a subclass which is inhereted by multiple viewcontrollers)?
How do I manage to set my first viewcontroller as the delegate of all the viewcontrollers in the tabbar - the SVC class, so I can get notified when the tabbarcontroller is dimissed and solve the error?
+[SVC setDismissDelegate:]: unrecognized selector sent to class 0xbca68
See the +
The plus sign idicates that you are calling a class method. You must have tried setting a class variable by a setter. But a property represents instance variables only. Therefore the setters and getters that are automatically generated are intance methods only. (starting with a minus - in error messages like this).
And that is what you do:
_SubclassVC = (SVC *) vc.superclass;
_SubclassVC.dismissDelegate = self;
For whatever reason (probably by mistake or misunderstanding) you take the vc instance and get its superclass. vc.superclass returns a class object, not an object (meaning not an instance, in Obj-C class objects are objects too).
Then you typecast it to (SVC *) just to stop the compiler from throwing errors (or warnings - not sure).
Well, I guess that you wondered yourself why you have to typecast it at all. That's the reason :)
Next, you assign self to a property dismissDelegate. The compiler does that because you typecasted it to SVC* which does have a property dismissDelegate. The compiler will actually call the setter setDismissDelegate as usual in contructs like this.
BUT at runtime the message (or selector) setDismissDelegate: is not sent to an SVC* but to a class object. And the class SVC does not have a method (or selector) +setDismissDelegate: and therefore cannot resolve the message. And that is exactly what the error message is telling you.
All right, now we get to your questions.
1. Well, it is not the way I would do it, but that is certainly one way of achiving it.
2. If you want to stick with that unusual approach then do this minor change and you will get rid of the error:
for (SVC *vc in navController.viewControllers) {
vc.dismissDelegate = self;
}
There is no point in fetching the superclass object. If you cannot access the property of a superclass then you did something wrong with the inheritance chain.
If you want to be on the save side:
for (UIViewController *vc in navController.viewControllers) {
if (vc isKindOfClass:[SVC class]){ //BTW, this is good usage of the class object
SVC *svc = (SVC*) vc;
svc.dismissDelegate = self;
}
}