iI'm very new in programming with multiple view controllers.
I have a RootViewController, and a FirstViewController. Inside RootViewController:
#property (nonatomic, retain) FirstController * firstController;
- (FirstController *)firstController {
if (!_firstController) {
UIStoryboard *storyboard = self.storyboard;
_firstController = [storyboard instantiateViewControllerWithIdentifier:#"First View"];
}
return _firstController;
}
So, I manage to see the FirstViewController that I've designed in the storyboard.
The problem is this. If I try to run this instruction inside RootViewController:
_firstController.myString = _myString;
myString is obviously the name of a property inside RootViewController and the same inside FirstViewController. Running this instruction doesn't work without allocating memory for _firstController. But, if I allocate memory and initialize _firstController, every adjustments made in the Interface Builder will be vanished.
How can I solve? I'm sure I'm trying to do something very wrong.
Thank you in advance.
You wrote getter - so use it!
self.firstController.myString = _myString;
instead of
_firstController.myString = _myString;
You're trying to access instance directly and of course there are no memory allocated for it because you are using technique called 'lazy loading'(ivar will be allocated when it needed.) By using self.firstController you simply send message [self firstController] which will allocate memory for first controller.
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 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 want to know which is the better way: Either to create a new uiviewcontroller for instance, if in a view i have to load a search view to search new element on a web server, or load a view that display the result from a request, Which is the better way?
This:
MasterViewController.h
#property (nonatomic, strong) PreviewViewController *reviewViewController;
MasterViewController.m
-(void)openPreviewView
{
if (!self.previewViewController) {
self.previewViewController = [[PreviewViewController alloc] init];
}
[self.navigationController pushViewController:self.previewViewController animated:YES];
}
or this:
MasterViewController.m
-(void)openPreviewView
{
PreviewViewController *previewView = [[PreviewViewController alloc] init];
[self.navigationController pushViewController:previewView animated:YES];
}
I always create each view controller as it is needed. The only time I keep a view controller around is if, after doing some proper performance testing, I find that keeping a specific view controller around is better for the user and for performance. Of course you must deal with memory warnings to properly cleanup any cached view controllers.
Don't worry about performance optimizations too early. Wait until you find, through real testing, that you actually have an issue to worry about.
The first one. iOS devices are resource-constrained, so in the absence of a compelling reason not to, you should always re-use if you can.
In my iOS app setup, I have a universal object (lets call it UniversalObj) declared in the top level object. (SampleAppDelegate in my case)
Within this, I have a UITabBarController containing two UIViewController (which I want to have access to UniversalObj.
In SampleAppDelegate, I have tried the following to pass UniversalObj to it:
MyViewController *vc = (MyViewController *) [self.tabBarController.viewControllers.objectAtIndex:0];
[vc setMyObj:self.universalObject];
The problem with this is that when I do this, sometimes, the view hasn't loaded yet so MyViewController.myObj==null.
Now, I am trying to access SampleAppDelegate (of type NSObject) from MyViewController. I can get to the UITabBarController using: ***self.parentViewController***, but I don't know how to get to SampleAppDelegate.
How can I access UniversalObj in SampleAppDelegate from MyViewController? How can I change the self.parentViewController line in MyViewController to get to SampleAppDelegate? Or is there a better way to do this?
Thanks in advance for your help.
Guvvy
You would use -[UINib instantiateWithOwner:options:]:
NSArray * topLevelObjects = [nib instantiateWithOwner:pwner options:options];
I found that I was able to access the object from MyViewController like this:
[self setMyObj:[(SampleAppDelegate *)[UIApplication sharedApplication].delegate universalObj]];
This example is taken from The Big Nerd Range iPhone book (page 143/144) - ItemsViewController is a subclass of UITableViewController:
#interface HomepwnerAppDelegate : NSObject <UIApplicationDelegate>
{
UIWindow *window;
ITemsViewController* itemsViewController;
}
....
itemsViewController = [[ItemsViewController alloc] init];
[window setRootViewController: itemsViewController]
My question is why is it necessary to have the iVar itemsViewController, why not just do this instead:
...
window.rootViewController = [[ItemsViewController alloc] init];
I presume the window will destroy its rootViewController when the app exits and thus there's no leaks, and the window will be in existence for the lifetime of the app so I don't understand why this and many other example have a separate iVar for the root controller?
TIA
The biggest advantage is simply that you can access the methods of your view controller without needing to cast over and over again:
[itemsViewController doSomething];
// vs.
[(ItemsViewController *)window.rootViewController doSomething];
Depending on the app you might need to refer to the root view controller frequently from the app delegate, for example when implementing the handlers for entering background/foreground and similar app delegate callbacks.
There is absolutely no need to keep the ivar around if you don't need it.
BTW, you will leak ItemsViewController if you don't autorelease it. (Unless you are using ARC)
The reason is historical, I think. Back when that book was written, the window and root view controller both used to be IBOutlets and were set from a nib file called MainWindow.nib.
Also, UIWindow didn't used to have a rootViewController property to assign the control to (the root view controller.view was just added directly as a subview to window), so if you didn't store it in an ivar then it wouldn't be retained by anything and your app wouldn't work because the root view controller would be released as soon as it was created.
These days however, since iOS4 and now ARC, the base project template has been updated and doesn't even have ivars any more (which are no longer needed). It does still have an #property for the view controller, but it's technically not needed any more either, and your alternative solution of assigning a new controller directly to the window.rootViewCOntroller would work fine.
This is totally stylistic choice. There are other ways to get the convenience accessor. I don't ever create an ivar in my rootViewController never changes. I will usually go for a read only property.
#property (nonatomic, readonly) MyRootViewController *rootViewController;
- (MyRootViewController *)rootViewController {
if ([self.window.rootViewController isKindOfClass:[MyRootViewController class]) {
return (MyRootViewController *)self.window.rootViewController;
}
return nil;
}