iPhone View Controller Register - iphone

Hi i'm pretty new to iPhone development, looking to put together a fairly substantial app and just wondering should View Controllers which are used later in the lifecycle of the app be registered in the AppDelegate at the start of just introduced as needed?
For example I start with a login page which requires a UINavigationController so I register with AppDelegate and i'm away, however following an intermediary page I'm
using a TabController so do I just introduce it on the 3rd page or register in AppDelegate?
More of an architectural best practice issue really :)

When the app launches, the main xib is loaded.
We basically provide the very first vie/view controller when the app launches in the app delegate in the function
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
This very first view controller could be UIViewController, UITabBarController, UINavigationController, etc. In short, any view controller.
From here, your application can proceed by showing new/other view controllers one after another in various ways like presenting a view controller modally, pushing a view controller (in case of UINavigationController), etc.

Well to answer your question short and simple. iPhone apps should use the least amount of memory as possible. So introducing a View Controller when needed is much less memory consuming then keeping everything open and running from start to end.
Hope that answers your question.

Generally, you should only instanciate classes that you need to save memory. If you create you views in code, a good way to do so is to use the getter method of a #property to create the class. For example, if you have a header file with:
#interface MyClass
#property (nonatomic, retain) UIView *myView;
#end
And an implementation file:
#implementation MyClass
#synthesize myView;
- (UIView *)myView {
if (myView == nil) {
myView = [[MyView alloc] init];
// do more initializations
}
return myView;
}
Then you can just access the view at any time, if it hasn't been created it will be, e.g.
[superView addSubView:self.myView];

Related

Why a separate instance of ViewController Class is affecting previous instance?

UPDATE - Cause found!... please read below & suggest the solution:
While creating a video to show this issue, I have found why does that happen...
Any Control/Element that is defined between #imports & #implementation DetailViewController in .m file is lost by the original detVC when a new instance is created of VC.
Example:
I am including a video that re-creates this issue. As shown, all controls work except a UISegmentedControl & a UILabel.. both of which are defined in DetailViewController.m as:
#import "DetailViewController.h"
UISegmentedControl *sortcontrol;
UILabel *incrementLabel;
#implementation DetailViewController
Video Link - http://www.youtube.com/watch?v=2ABdK0LkGiA
I think we are pretty close.. Waiting for the answer!!
P.S.: I think this info is enough can lead us to the solution, but if needed I can share the code too.
EARLIER:
There's a detailViewController (detVC) in our app which is normally 'pushed' from a UITableViewController.
Now we are also implementing Push Notifications which would 'modally' present the same detVC whenever the notification arrives, which in turn can happen over any view controller.
It works all fine until the detVC through notification is presented while another instance of detVC was already the active view controller (pushed earlier through TableView).
The problem happens when the presented detVC is dismissed - the pushed detVC 'looses' control of about everything - It is not pointing to the same data as earlier & even the UISegmentedControl on Navigation Toolbar points to -1 index on pressing any index!
Why does such thing happen when we are creating a separate instance of detVC? Why does it affect the earlier detVC? How can it be resolved / What about Objective C needs to be learned here? (FYI, we are using ARC in the project)
Thanks
EDIT - Some code:
When Pushed:
ProductDetailViewController *detailViewController = [[ProductDetailViewController alloc] init];
detailViewController.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:detailViewController animated:YES];
detailViewController.serverOffset=serverOffset;
detailViewController.prdctIndex = indexPath.row;
When presented through Notification:
- (void)displaySingleProduct:(NSDictionary*)userInfo{
ProductDetailViewController *onePrdVC = [[ProductDetailViewController alloc] init];
UINavigationController *addNav = [[UINavigationController alloc] initWithRootViewController:onePrdVC];
onePrdVC.justOne=YES;
onePrdVC.onePrdIDtoLoad=[[[userInfo valueForKey:#"prd"] valueForKey:#"id"] intValue];
[self.navigationController presentModalViewController:addNav animated:YES];
}
There is a Product class in detVC which gets the data from the values stored at the prdctIndex row in the SQLite table.
After dismissing the presented detVC through notification, the Product class of the pushed detVC starts pointing to the row which was used for Product class in presented detVC. And there is no such code in viewDidAppear which would alter Product class.
So, this, UISegmentedControl issue mentioned above & some other problems are causing the pain! Basically, the whole detVC acts weird - which re-works if we just pop & re-push it!
EDIT 2
I have more info on it which could lead to the cause.
If I run viewDidLoad on viewDidAppear like this:
if (!justOne) {
aProduct = [allProducts getProduct:(prdctIndex+1) one:NO];
[self viewDidLoad];
[self populateDetails];
}
the pushed detVC regains the control & every element/control starts re-working as expected (but not in the ideal way). So, it is quite clear that the separate instance of presented detVC does messes up the earlier pushed detVC & a re-setting up of all the controls / pointers is required through viewDidLoad.
Can any helpful conclusion can be derived from this info?
When in your code you write:
#import "DetailViewController.h"
UISegmentedControl *sortcontrol;
UILabel *incrementLabel;
#implementation DetailViewController
You are not defining these variables as instance variables (ivars), so they are not individual for each instance. There are three ways to define ivars.
1) The traditional way, in your .h file.
#interface DetailViewController{
UISegmentedControl *sortcontrol;
UILabel *incrementLabel;
}
2) Some additions to objective-C have added support for the next two ways. Like declaring them in your .m file.
#implementation DetailViewController{
UISegmentedControl *sortcontrol;
UILabel *incrementLabel;
}
3) If these ivars use properties to expose them, then you can simply leave out the explicit definition of them. So in .h:
#interface DetailViewController
#property (strong, nonatomic) IBOutlet UISegmentedControl *sortcontrol;
#property (strong, nonatomic) IBOutlet UILabel *incrementLabel;
and then in the .m file:
#implementation DetailViewController
#synthesize sortcontrol;
#synthesize incrementLabel;
Are you loading the view controller from a NIB? If so, it may be that the same instance of the view controller is used each time. You can log the address of your view controller in viewDidLoad and see if this is the case.
It actually depends on how your apps architecture has been created. If you are using TabBarController based application, here is the proper way to do so.
If two instances of the same class are interfering with each other, that strongly suggests that you are not storing all your state in instance variables. I would suspect that something in ProductDetailViewController stores its state in global or class data, and that your problem lives in there.
It's possible that you have done something silly like making ProductDetailViewController a forced singleton (overriding +alloc, which you should almost never do), but generally people remember when they've done that.
Never call viewDidLoad directly. That's only for the system to call. (I know you were just testing, but don't leave that in.)
But are you calling [super viewDidAppear:animated] in your viewDidAppear:? You have to call your superclass in that method.

Is a UINavigationController required to do pushViewController?

I think the answer to my question is "Yes", but i just would like a confirmation from everyone.
I have a UITableView which should "slide" to another UIViewController when i select a row.
I've done this before with other apps (and they've included the UINavigationController, though in a UITabView).
So im wondering:
Is a UINavigationController required in order to use the self.navigationController pushViewController?
If so (which again i suspect to be true), where must I define the UINavigationController?
In the AppDelegate? In the main/primary UIViewController?
Yes, you will need to have a UINavigationController in order to do pushViewController. However, there is no one place where you must define it. If every view controller in your whole application is part of one UINavigationController, then it makes sense to put it in the App Delegate. You can, however, allocate and use a view controller at any time. (Notice that it is a UIViewController subclass.) You can also have multiple UINavigationControllers in your app (which is common, for example, if you have a Tab bar). So you can create UINavigationControllers at any time.
If you are looking for alternate ways of present other view controllers, you do have some different options. For example,
- (void)presentModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated
will allow you to transition from one view controller to another view controller, and can be used without a UINavigationController. But I think the Navigation Controller is very often the best way to move from one view controller to another.
The short answer is: Yes.
UINavigationController was designed with the purpose of managing and animating a stack of view controllers on and off the screen (in conjunction with a few other interface elements such as a navigation bar, or a toolbar). Traditionally, UINavigationControllers are strongly held and initialized by the App Delegate, as they are considered a top-priority root object. An example class showing the proper usage of a UINavigationController might look like this:
#import <UIKit/UIKit.h>
#interface CFIExampleAppDelegate : NSResponder <UIApplicationDelegate>
#property (nonatomic, strong) UIWindow *window;
#property (nonatomic, strong) UINavigationController *navigationController;
#end
#implementation CFIExampleAppDelegate
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc]initWithFrame:UIScreen.mainScreen.bounds];
self.navigationController = [[UINavigationController alloc]initWithRootViewController:/*Some Controller Instance*/];
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}
#end
The long answer, as always, is No.
UINavigationController can be trivially reimplemented (and it has, many times) to great effect. It's as simple as subclassing UIViewController and bolting on some kind of stack (like an NSMutableArray).

Question about the mechanics of iPhone view controllers (i.e., explain why this crashes)

I am pretty new to iPhone programming, and was playing around with an app yesterday trying different scenarios with view controllers and nib files. So, I started a new app with a FirstViewController (FVC for short) and an FVC.xib.
I layed out a quick view in FVC.xib and ran the app - view displays, great.
I now wanted to have a second view I could add on top of the main view. So I went ahead and created SecondViewController.xib (SVC) but did not create the .m and .h files. I went about trying to load both these views from the same view controller, and here is where my question lies:
I created a button in FVC.xib and created an IBAction like this:
- (IBAction)loadSVC {
FirstViewController *viewController = [[FirstViewController alloc] initWithNibName:#"SecondViewController" bundle:[NSBundle mainBundle]];
secondView = viewcontroller.view;
[viewController release];
[self.view addSubView:secondView];
}
So this works great and adds the contents of SVC.xib, but when I try and remove that view from the superview, the app crashes:
[secondView removeFromSuperview];
If I actually create a view controller for SVC, use that to instantiate my view in FVC, and move the remove code to the SVC:
[self.view removeFromSuperview];
Everything works. My question - I kind of get why my first method crashes, but I was hoping someone could explain why and what goes on behind the scenes. I'm still a noob with object oriented programming, so what is actually happening in my first case where I create a new instance of FirstViewController and add its view to self.view? Why can't I release it (I assume because the original view is associated with FirstViewController, and when I create a new instance with the second xib it messes everything up) - I'd love a more technical explanation as to what is happening...
Thanks much!!
EDIT to add more info in response to Nick's reply below
Nick - so your answer did clear my thinking a bit in regards to the retain count, etc... I did another test app trying to get this working from a single view controller - think, for example, that I wanted to display an Alert or Welcome message to the user (I know in a real app there are different methods to accomplish this, but this is more of a learning experience) -- so I have my main view # MainViewController and layout my alert message in a xib called alert.xib -- so there is no logic behind the alert message, no reason for it to have a view controller that I can see, my end goal being loading/unloading this on top of my main view from the main view's view controller (or understanding why it is impossible)
I tried this using instance variables as you recommended:
In MainViewController.h:
#import <UIKit/UIKit.h>
UIViewController *secondController;
UIView *secondView;
#interface MainViewController : UIViewController {
}
#property(nonatomic, retain) UIViewController *secondController;
#property(nonatomic, retain) UIView *secondView;
- (IBAction)loadSecond;
- (IBAction)removeSecond;
#end
In MainViewController.m:
#import "MainViewController.h"
#implementation MainViewController
#synthesize secondController, secondView;
- (IBAction)loadSecond {
secondController = [[MainViewController alloc] initWithNibName:#"alert" bundle:[NSBundle mainBundle]];
secondView = secondController.view;
[self.view addSubview:secondView];
}
- (IBAction)removeSecond {
//I've tried a number of things here, like [secondView removeFromSuperview];, [self.secondView removeFromSuperview];, [secondController.view removeFromSuperview];
}
- (void)dealloc {
[secondController release];
[secondView release];
[super dealloc];
}
So - this works to load the alert view, but the removeSecond button does nothing (I did use NSLog to verify the removeSecond method is fired) - why?
Second, and most importantly - is this even possible, or is it horrible practice? Should every nib/view I am manipulating have their own view controller? Am I wrong to think I could just make a new instance of MainViewController and use it to display and remove this no-functionality, very temporary view? (And yes, I realize I could easily create this view programatically or accomplish the end goal in many different ways which would be easier, but I'm trying to really learn this stuff and I think figuring this out will help...
Thanks for the help!
You created a view controller
You accessed its view which caused controller to create the view and call the delegates (i.e. viewDidLoad)
Controller returns the view that you asked for
Now you add the view as a subview which increases its retain count
Controller is released and it releases the view, BUT since view's retain count was increased the view is still there
You try to remove the view, it is unloaded and delegates are to be called (e.g. viewDidUnload), however that messes up since the controller who created the view is released and that piece of memory is... smth else :)
That's why the first method doesn't work.
The second method is NOT correct either but it works because:
You remove controller's view from superview but since controller itself is not released (you didn't call [self release] or anything like that, not saying that you should :), just an example), then the view didn't reach 0 (zero) retain count and is still there - which means its subviews aren't removed
The proper way to do it is to save the reference to the controller as an instance variable (usually declare a synthesized property), and release it only when you are done with the view, making sure that the view is removed from superview before hand. The default templete for a View Based App shows how view controller should be managed
Hope this helps to understand why both methods behave differently
Based on your clarifications, you don't need secondView property or iVar. Also in your loadSecond instead of secontController = bla you need self.secondController = bla, otherwise you simply assign reference to the iVar instead of going through the setter.
Yes, it's possible to load subviews/other resources from a nib without having a dedicated controller
This is how you do it (one of the approaches):
UIView *result = nil;
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"MyNibName" owner:owner options:nil];
for ( id o in bundle ) {
if ( [o isKindOfClass:[UIView class]] ) {
result = (UIView *)o;
break;
}
}
Here the result will contain the first UIView in MyNibName. You can use other criteria to find out whether you got the view you wanted (tags, types...)

Using Interface Builder efficiently

I am new to iPhone and objective c. I have spent hours and hours and hours reading documents and trying to understand how things work. I have RTFM or at least am in the process.
My main problem is that I want to understand how to specify where an event gets passed to and the only way I have been able to do it is by specifying delegates but I am certain there is an easier/quicker way in IB.
So, an example.
Lets say I have 20 different views and view controllers and one MyAppDelegate.
I want to be able to build all of these different Xib files in IB and add however many buttons and text fields and whatever and then specify that they all produce some event in the MyAppDelegate object. To do this I added a MyAppDelegate object in each view controller in IB's list view. Then I created an IBAction method in MyAppDelegate in XCode and went back to IB and linked all of the events to the MyAppDelegate object in each Xib file.
However when I tried running it it just crashed with a bad read exception.
My guess is that each Xib file is putting a MyAppDelegate object pointer that has nothing to do with the eventual MyAppDelegate adress that will actually be created at runtime.
So my question is...how can I do this?!!!
If you create an instance of MyAppDelegate in each nib file then, yes, you do end up with a lot of different instances of the class when all the nibs load. The app delegate is not identified by class or even protocol but rather by being the object pointed to by the application instance's delegate property. To find the true app delegate, you have have to ask the application object itself for its delegate
You should have all your view controllers descend from a parent view controller class that has an appDelegate property. Implement something like this:
#import "MyAppDelegateClass.h"
#interface ViewControllerBaseClass :UIViewController {
MyAppDelegateClass *appDelegate;
}
#property(nonatomic, retain) *appDelegate;
#end
#implementation ViewControllerBaseClass
#synthesize appDelegate;
-(MyAppDelegateClass *) appDelegate{
self.appDelegate=(MyAppDelegateClass *)[[UIApplication sharedInstance] delegate];
return appDelegate;
}
#end
When the view controller needs the app delegate it just calls self.appDelegate. If you want to access an attribute of the app delegate use self.appDelegate.attributeName.
The important thing is that you ask the application for its specific delegate instance at runtime. You can't do that from a nib file.
I'm not entirely clear what exactly you're trying to do, but it's probably a bad idea. There should only be one app delegate per application, and it should deal with behavior for the whole application. Typically, the app delegate initializes the root view controller(s) and displays them, but not much else (other than handling things like opening and saving data sources).
The view controllers (subclasses of UIViewController) should interact with the XIBs. Having the view-specific behavior in the view controllers makes the app much easier to manage and maintain. Typically, there should be 0 or 1 XIBs per view controller (more than that is complicated). You set up the interaction with the views using the Target/Action pattern with IBOutlets and IBActions (see here for a complete guide). It's generally a bad idea to make view controllers or XIBs dependent on the app delegate (since reducing dependencies again makes the code easier to manage).
In general you should be making a view controller for each of the views you are building, and link events to those view controllers - not the app delegate. In fact usually no event ever is wired to the app delegate from any nib file, even in the sample projects you'll note that view controllers are created and held onto by the app delegate, but it does not receive events.

iPhone Views at Runtime?

I am new to the iPhone SDK and am trying to create 3 views and switch between them. Data will come from a server and I will basically be showing 1 view and caching the other two. So far I am just trying to create a view and display it at run-time. My code is listed below. It shows only a blank screen and I think I am missing a key concept. Any Help?
#import <UIKit/UIKit.h>
#import "ImageViewController.h"
#interface Test5ViewController : UIViewController
{
IBOutlet UIView *rootView;
ImageViewController *curImage;
ImageViewController *nextImage;
ImageViewController *prevImage;
}
#property(nonatomic,retain) IBOutlet UIView *rootView;
#property(nonatomic,retain) ImageViewController *curImage;
#property(nonatomic,retain) ImageViewController *nextImage;
#property(nonatomic,retain) ImageViewController *prevImage;
#end
and
- (void)loadView
{
self.curImage = [[ImageViewController alloc]initWithNibName:#"ImageView" bundle:[NSBundle mainBundle]];
UIImage *pic = [UIImage imageNamed:#"baby-gorilla.jpg"];
[self.curImage assignImage:pic];
self.rootView = self.curImage.view;
}
and
#import <UIKit/UIKit.h>
#interface ImageViewController : UIViewController
{
IBOutlet UIImageView *image;
}
-(void)assignImage:(UIImage *)screenShotToSet;
#property(nonatomic,retain) IBOutlet UIImageView *image;
#end
Welcome to the iPhone SDK!
In general, there are two ways to get any view displayed.
First, and most commonly, you use a NIB file created by the Interface Builder. This is usually the easiest way to get started and I would recommend it for what you're trying to do here. It's too lengthy to describe all the steps you need to do for what you have here, but basically start in xcode by creating a new file and selecting "user interfaces" and choose View XIB. This will create a basic NIB file (they're called NIBs rather than XIBs for historical reasons). The first step in interface builder is to change the class name of the "File's Owner" to your UIViewController subclass (Test5ViewController). You can then drop anything that IB will allow into the view window or even replace the pre-supplied view object with one of your own. And here's the trick: make sure the view outlet (supplied by the UIViewController superclass) is connected to a view. Once this is done, this view will be automatically loaded when your NIB is loaded. You can then just put your UIViewController subclass (Test5ViewController) in your MainWindow.xib NIB file to get it automatically loaded, and you're in business.
Now, the way you're doing it here is the second way. Some people like to code this way all the time and not user interface builder. And while it's definitely necessary sometimes and always more flexible, it makes you understand what is happening a bit better. There may be other things, but the main thing you're missing is that in your code above, you have nothing that is adding your view into the view hierarchy. You need to check first that you have an UIApplicationDelegate subclass and it needs to load your "root" UIViewController class. All initial project creation types in xcode do this (except Window-based application). It is code like:
[window addSubview:rootController.view];
Once this is done, if your view controller wasn't loaded by the NIB (described briefly above), your loadView method will be called, expecting you to build your own view hierarchy. Above, you created the view(s), but failed to put them in a hierarchy. You need something like:
[self.view addSubview:curImage.view];
No view will be rendered until added to the view hierarchy. Make sure to look up the UIView class in the documentation and understand the variety of ways to add and remove views to the view hierarchy.
A couple things I should warn you about:
* your code above is leaking. You need to review how objective-C properties work. There's lots on this site about it. More than I have time to write about here.
* don't create a rootView property in the case you have here. There already is one in the superclass (UIViewController). It's just 'view'. Use that for saving your root view.
I hope this helps you get started. It can be bewildering at first, but you'll soon get it going! I recommend building and rewriting and rebuilding a lot of sample code before you do your "real" application. The SDK has many great samples.