I am making master detail application, i have dynamic Detail ViewController. Detail ViewController are changed.
But in every Detail ViewController I have one common method updateInfo I want to call that method
Here is my code
UINavigationController *nav=[self.splitViewController.viewControllers objectAtIndex:1];
UIViewController *controller=[nav.viewControllers objectAtIndex:0];
[controller updateLastInfo];
But it gives me error no method found.
it will work if i use UIViewController name.
HomeViewController *controller=(HomeViewController)[nav.viewControllers objectAtIndex:0];
[controller updateLastInfo];
But i dnt want to do above things.
I have tried to explain. Please help
You can use id
UINavigationController *nav=[self.splitViewController.viewControllers objectAtIndex:1];
id controller=[nav.viewControllers objectAtIndex:0];
[controller updateLastInfo];
You could subclass UIViewController and make a base DetailViewController class that houses common functionality of your detail view controllers. Then you would make all of your detail view controllers subclass DetailViewController instead of UIViewController. This would be a safe way to do it and would also allow you to add extra functionality to your updateInfo method in the specific detail view controllers.
If you want an unsafe way, you could make your controller object of type id. I wouldn't suggest this approach as it relies on your personal knowledge of the code. If someone else (or yourself down the road) sets it to a view controller that doesn't have that method, the code will still try to run and will crash.
UIViewController doesn't have a method named updateInfo, so the compiler will of course complain when you try to send that message to a pointer that's known only to point to an instance of UIViewController. When you use the class name, like this:
HomeViewController *controller=(HomeViewController)[nav.viewControllers objectAtIndex:0];
you're providing more information to the compiler, using a type cast to tell it "Hey, don't worry, I know for certain that the object I'll get back is a HomeViewController. Since you seem to have several types of view controllers that all have this method, the best thing to do is to declare the updateInfo method in a protocol and then have each of those UIViewController subclasses implement that protocol. So, your protocol declaration would be in a header file and might look like:
#protocol SomeProtocol
- (void)updateInfo
#end
and each class that has an -updateInfo method would just need to declare that it adopts the protocol:
#interface HomeViewController <SomeProtocol>
//...
#end
and then make sure that you have an -updateInfo in your class implementation:
#implementation HomeViewController
- (void)updateInfo {
//...
}
//...
#end
Then, in your code, you can either check that the object conforms to the protocol using -conformsToProtocol: like this:
if ([controller conformsToProtocol:#protocol(SomeProtocol)]) {
UIViewController<SomeProtocol> *c = (UIViewController<SomeProtocol>*)controller;
[c updateInfo];
}
or else just check that the object responds to the selector before calling it:
if ([controller respondsToSelector:#selector(updateInfo)]) {
[controller performSelector(updateInfo)];
}
The other answers you've received (using id or creating a common base class) are also good ones, but to be safe make sure you do some checking before calling your method. For example, you can use -isKindOfClass to make sure that the view controller you get back is in fact an instance of your common base class, and you can use -respondsToSelector: as above to check that an id points to an object that implements updateInfo.
Related
SettingsStore.h
#interface SettingsStore : IASKAbstractSettingsStore
{
#public
NSDictionary *dict;
NSDictionary *changedDict;
}
- (void)removeAccount;
#end
menuView.m
-(IBAction)onSignOutClick:(id)sender
{
SettingsStore *foo = [[SettingsStore alloc]init];
[foo removeAccount];
[self.navigationController pushViewController:foo animated:YES];
exit(0);
}
I want to call this removeAccount function from menuView.m. But I am getting error.
How to fix it and call this removeAccount.
There are few mistakes in your Code please find them below.
[foo removeAccount]; Calling this method is correct
[self.navigationController pushViewController:foo animated:YES];
Not correct because SettingsStore is not subclass of
UIViewController only subclass of UIViewController can be pushed to
Navigation controller
exit(0); Calling this method is not
recommended by Apple
You are calling removeAccount correctly from your menuView.m file, but there are several issues with your code:
You are treating foo as though it were a UIViewController, and it's actually a member of the SettingStore class. Does the SettingStore class refer to an actual screen, or is it more a data object (for storing settings?). If it's the latter, you don't want to push it on. You can create it, and use it, but the user doesn't need to see it.
You are calling exit(0); you can remove that line. If you want to remove the menuView.m file from your memory, remove references to it (e.g. from its parent view controller).
The menuView.m file is confusing, as in, is it a view or a viewController. An IBAction I would normally stick in a ViewController file, rather than a view file. Your basic design pattern is MVC (Model / View / Controller). In this case, it seems your SettingStore file is a Model (data), the menuView.m is a View and your code is for the Controller bit.
I am trying to do some debugging on a project I am working on, and would like to know the kind of ViewController a specific variable is being assigned. So I created an if statement like so:
if ([controller isSubclassOfClass:[UINavigationController class]]) {
NSLog(#"It's a navigation controller!");
}
'controller' is created just above using
DetailViewController *controller = (DetailViewController *)[[segue destinationViewController]topViewController];
DetailViewController is a simple class inheriting from UIViewController. However I'm getting a compiler error saying No visibile #interface for 'DetailViewController' declares the selector 'isSubclassOfClass:'
How is that possible? When I tripleTap the reference for isSubclassOfClass it says it's a class method defined in NSObject. How is it possible that DetailViewController doesn't know that selector since all objects inherit from NSObject??
Both other responders gave you a correct answer to your question, but I wanted to clarify something.
Methods who's declaration start with a + are CLASS methods. The class object implements those methods, not instances of that class. So this method:
+ (BOOL)isSubclassOfClass:(Class)aClass
Is a class method.
You'd use it like Jeffery Thomas demonstrated in the first part of his answer:
[[controller class] isSubclassOfClass:[UINavigationController class]]
The [controller class] bit fetches the class object for the controller object, and then sends that class object the isSubclassOfClass message.
In contrast, the method isKindOfClass:
- (BOOL)isKindOfClass:(Class)aClass
...is an instance method. You can tell because it's declaration starts with a "-" instead of a "+". Learn to look for the "+" or "-" at the beginning of every method declaration to see if it's a class method or an instance method. And if the compiler isn't letting you send a message that you see in the docs, go back and double-check. I've been programming in Objective-C for quite a few years now and this still bites me occasionally.
You send the isKindOfClass message to an instance of the object you want to test for class membership:
[controller isKindOfClass:[UINavigationController class]]
The standard way of doing this is -isKindOfClass:, not +isSubclassOfClass:, but it's possible to use +isSubclassOfClass:.
Using the instance method -isKindOfClass:
if ([controller isKindOfClass:[UINavigationController class]]) {
NSLog(#"It's a navigation controller!");
}
Using the class method +isSubclassOfClass:
if ([[controller class] isSubclassOfClass:[UINavigationController class]]) {
NSLog(#"It's a navigation controller!");
}
When I tripleTap the reference for isSubclassOfClass it says it's a class method defined in NSObject.
It's a class method, and you're using it on an instance. Try isKindOfClass.
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;
}
}
I have a UIViewController class and a UITableViewController class. Within the UIViewController class I have an NSMutableArray.
I now have the issue of how to load data into my table view, a separate class, I must access the NSMutableArray I used to populate the previous UIViewController class.
I tried using a delegate to access the array in the UIViewControllerClass however the array had "0 objects" and was NULL
I would appreciate some guidance in the right direction here.
You could have one view controller hold a reference to the other view controller and query the public NSMutableArray on it for data. Aaron suggested this and it might be your best solution.
Or.. you have multiple view controllers trying to access the same set of data. Potentially you have other classes which will want to access this data also. You might want to consider pulling the data out of the view controller and storying it in a neutral location. You could store it in the AppDelegate and then reference the app delegates from any place you need it.
id<UIApplicationDelegate> appDelegate = [UIApplication sharedApplication].delegate;
NSMutableArray *myData = appDelegate.data;
You could also consider pulling all the logic of your data and the data itself into a separate class and use a Singleton It would allow you to access/manipulate the data fairly easy from anywhere.
The last 2 methods would insulate data from user interface controller objects and prevent the need from potentially unrelated objects needing to hold references to one another. Used properly it will reduce code complexity and mage future changes easier to manage.
Create an NSMutableArray property on your UITableViewController class like so:
#interface CustomTableViewController : UITableViewController
#property (strong, nonatomic) NSMutableArray *dataFromOtherClass;
#end
And then when you transition, perhaps like this, you can set the dataFromOtherClass property:
CustomTableViewController *controller = [[CustomTableViewController alloc] initWithNibName:#"CustomTableViewController" bundle:nil];
controller.dataFromOtherClass = myNSMutableArrayData; // <-- Set data like this
[self.navigationController controller animated:YES];
// Or ...
[self presentViewController:controller animated:YES];
// Etc...
In my project I have 3 controllers;
NavigationController
ServiceTableViewController
DateTableViewController
The ServiceTableViewController is the initial view controller. It has several rows which prompt the user to enter in data, which will be emailed to a particular email address. One of the rows, when tapped, sends the user to the DateTableViewController which prompts the user to select a date from the UIDatePicker.
The issue I am facing is getting data back from DateTableViewController in order to display a label on the ServiceTableViewController to show the date the user selects in the DateTableViewController. I know how to get information from one view controller to another, but to go in reverse, so to speak, is not something I know how to do. Any help is appreciated.
Take a look at this:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/CommunicateWithObjects.html
There are couple of ways to pass data back and forth between view controllers.
Delegates
Target-Aciton
Notification
KVO
but honestly delegates are really all you need really and it sounds like in your current case.
see this -> (Passing Data between View Controllers)
Having said that, if you use delegates, here is how ---
setup a protocol in DateTableViewController.h at the top like so:
#protocol DateTableViewControllerDelegate <NSObject>
- (void)userSelectedThisDate:(NSDate *)d;
end
put this with the other properties
#property (nonatomic, weak) id <DateTableViewControllerDelegate> delegate;
and in DateTableViewController.m with the date to send back
[self.delegate userSelectedThisDate:withTheDateToSendBack];
in and ServiceTableViewController.h add
#import "DateTableViewController.h"
#interface ServiceTableViewController : UIViewController <DateTableViewControllerDelegate>
and since you are UINavigationController, somewhere in ServiceTableViewController.m add this when you are about to push to the DateTableViewController
DateTableViewController *vc = [[DateTableViewController alloc] init];
self.delegate = self;
[self.navigationController pushViewController:vc animated:YES];
and finally put the delegate method in ServiceTableViewController.m
- (void)userSelectedThisDate:(NSDate *)d {
NSLog(#"%#", d); // this should show the returned date
}
Research delegate pattern (here) (a heavily used pattern within Apple frameworks). You want to define a delegate protocol which allows to a date to be passed to the delegate.
You could implement the pattern as an #protocol with a single method and a property on the DateTableViewController. The ServiceTableViewController sets itself as the delegate before pushing the DateTableViewController.
Or, you could implement using a block. Again, the ServiceTableViewController sets the block before pushing the DateTableViewController.