UIApplicationDelegate in Interface Builder with Xcode 4.2 - iphone

My application is a view-based application. I need to create a UINavigationController as my rootViewController.
In the former version of Xcode, there was a xib file named mainWindow in which we could:
Connect our UIApplicationDelegate implementation to the UIApplication's delegate outlet
Connect a UIWindow to the UIApplicationDelegate
Connect a UIViewController to the UIWindow's rootViewController property.
But now (Xcode 4.2) does not create this xib file!
So how can I create a custom UINavigationController and connect it to my UIApplicationDelegate implementation in InterfaceBuilder?
Here is my code in my UIApplicationDelegate implementation:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
SDWebImageRootViewController *root = [[SDWebImageRootViewController alloc] initWithNibName:nil bundle:nil];
_navigationController = [[[UINavigationController alloc] initWithRootViewController:root] autorelease];
self.window.rootViewController = _navigationController;
[[_navigationController navigationBar] setBarStyle:UIBarStyleBlack];
[[_navigationController navigationBar] setTranslucent:YES];
[_window addSubview:[_navigationController view]];
}

First of all, yes, you still can use IB for this even in the latest version of Xcode, so I'm not sure where you're getting that from.
If you want to know how to specify your application delegate without IB, that's fairly simple:
In your main method, simply use your application delegate's class name as the 4th parameter to UIApplicationMain:
int main(int argc, char *argv[])
{
#autoreleasepool
{
int retVal = UIApplicationMain(argc, argv, nil, #"APPLICATION_DELEGATE_CLASS_NAME");
return retVal;
}
}
In fact, Xcode 4.2 does this for you by default when you create a view-based application from the template (although it doesn't use a static string... which is probably better than my suggestion honestly because it would get refactored if you use the built-in refactoring, etc):
NSStringFromClass([AppDelegate class])
To answer your followup question: ok , then what should I do ? to connect the UINC outlet in interface ?
Don't bother.
Still don't believe me? Fine... here's a tutorial... it's FIFTEEN steps! ... and absolutely pointless.
First, create the application:
Open up main.m and replace the fourth parameter with nil:
Create a new xib, or hijack the existing one (which I'm going to do here), and make the File's Owner class UIApplication.
Next, add an Object from the toolbox and change the class to your UIApplicationDelegate subclass:
Now, switch back over to the File's Owner and connect the delegate outlet to the UIApplicationDelegate you added. Remove the view reference if you're doing what I did and hijacked the existing xib.
Now add a UIWindow from the toolbox.
Now add a 'UIViewControllerfrom the toolbox. This is your customUIViewController. If you want aUINavigationControllerorUITableViewController` just add that instead.
If using a custom UIViewController class, specify the class here:
Connect the UIWindow's rootViewController outlet to your UIViewController:
Crack open your UIApplicationDelegate interface and make or modify the window property to make it an IBOutlet.
Switch into the implementation and remove any "worthless" code that sets up your window and root view controller. (I'm being sarcastic here... this succinct code does everything we are doing here... just programmatically instead of through IB.)
Switch back to your xib and hookup your UIApplicationDelegate's window outlet.
Now, in your target's deployment info, set your xib as the "Main Interface":
Done.... pshew!

Related

IBAction not functioning after I renamed xib file

I have an universal application that had all views in the same file MainWindow.xib. Today I decided to separate these views into their respective xib files (to have MainMenuController.h, MainMenuController.m and MainMenuController.xib for example). Now I can't receive and IBActions. Here is what I did step by step:
I created a new .xib file named MainMenuController.xib and set it's File's Owner to the already existing MainMenuController class.
I copied the view corresponding to the MainMenuController from the MainWindow.xib file and pasted it into the newly created MainMenuController.xib. I set the File's Owner's view to be the newly pasted view (connected in IB).
In the info.plist, I removed the entries for the "Main xib file base name", so the app doesn't open with MainWindow.xib automatically.
I modified the app delegate to create the window programatically and add the MainMenuController to it with this code:
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
mainMenuController = [[MainMenuController alloc] init];
self.window.rootViewController = mainMenuController;
[self.window makeKeyAndVisible];
[mainMenuController release];
"mainMenuController" and "window" are instance variables and also declared as properties.
I have only one AppDelegate class and main.m contains:
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}}
Now when the application opens, I see the MainMenuController's view come up. At this point I began to wire the IBOutlets and IBActions in the pasted view in the new xib file. Although I see the IBOutlets in the File's Owner and connect them properly, the function for the IBAction is never called when I press the button.
Possible errors that came to my mind:
(1) application window not properly set, it doesn't pass events,
(2) something is stuck or lost while copying the view, still pointing to the old owner
(3) something wrong stuck in xcode project
What do you think the problem could be? Is it one of the above? How can I solve this? Any help is appreciated.
Thanks in advance.
Your creation of the main view controller:
mainMenuController = [[MainMenuController alloc] init];
makes no reference to the XIB. So you're doing a purely programmatic creation with no reference to whatever may or may not be in the XIB. Hence your view controller appears to work but none of the outlets or actions are wired up. You may be making reference elsewhere, but I guess not appropriately.
Probably you want:
mainMenuController = [[MainMenuController alloc]
initWithNibName:#"MainMenuController" bundle:nil];
That'll explicitly use whatever is in the XIB to create the controller.

iPhone: unable to start simple application

I'm trying to make an iPhone app here, and I've gotten it down to a simple HelloWorld problem. For some reason, the following does not work in XCode 4.4. I'd really appreciate figuring out what's going on.
I follow these steps:
Start an 'empty project' type
Name it
Add in a new objective-c class with a .xib. Say this new view controller is StartViewController, so I now have StartViewController .xib, .h, and .m.
Check: file's owner for the .xib matches the .h file. It does in IB.
Change the background of the .xib to something other than black (I like stripes).
Add these lines to the main app delegate:
import "StartViewController.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
StartViewController* theController = [[StartViewController alloc]init];
[self.window addSubview:theController.view];
[self.window makeKeyAndVisible];
return YES;
}
And the app immediately crashes on running with:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the "StartView" nib but the view outlet was not set.'
How can I make it work?
EDIT: PS, I have no idea why the code formatting appears to have failed. I was under the impression that it was just four spaces at the beginning of a line...
Do what am error tells you to do. Set an outlet for Startview in your VC.
I'm answering this because it's so ludicrous.
Turns out that order matters.
I deleted the old controlling files, and then added 'startingviewcontroller', edited the appdelegate function to be startingviewcontroller, then it all works. Not sure why that should matter, but hey.

Set a UITabBarController delegate in a UIStoryboard

I've been searching for this, can't find the answer.
I'm setting up a UIStoryboard in Interface Builder, and while everything is working fine, I seem to be unable to hook up the delegate outlet of the UITabBarController to any of the UIViewController's in the UIStoryboard, regardless of their position in the UIStoryboard. I've set the UIViewController's to be a <UITabBarControllerDelegate> in the .h file, but Interface Builder won't allow me to select the UIViewController as the delegate for the UITabBarController.
Has anyone run into this issue?
It turns out that the reason you can't set the delegate in the UIStoryboard is because you have no guarantee that the UIViewController is loaded before the UITabBar is loaded. Therefore, programmatically setting the delegate (in a different UIViewController) is the ONLY way to accomplish this.
You'll need to do it programmatically in the application:didFinishLaunchingWithOptions method of your application delegate:
_tabBarController = (UITabBarController *)_window.rootViewController;
_tabBarController.delegate = self;

iPhone View Controller Register

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];

What is the relationship between AppDelegate, RootViewController, and UIApplication?

I am trying to figure out the relationship between the appdelegate, RootViewControoler, and UIApplication. Here is what I kinda have figured out so far:
When starting your application up, main.m gets loaded.
From here, your MainWindow.xib gets loaded.
In your MainWindow.xib, your File's Owner is of type UIApplication.
You set your UIApplication's delegate to your AppDelegate.
In your AppDelegate's source code, you can set your RootViewController to be the first view shown.
Is this right? What prompts AppDelegate to initially run it's
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { }
method?
When an Objective-C application starts, it starts by running the function named main(). It doesn't have to be in the file "main.m" but that's how the Xcode wizard sets things up.
Inside the wizard-produced main() function, there is this line:
int retVal = UIApplicationMain(argc, argv, nil, nil);
That is what starts the "UIKit" framework that makes up the entire application. Inside UIApplicationMain, an object of type UIApplication is created. And part of what UIApplication does when the application starts is call the applicationDidFinishLaunchingWithOptions method on the delegate member of the UIApplication class. This delegate is set up in the MainWindow.xib file to be an instance of your ProjectAppDelegate class, a subclass of NSObject that conforms to the UIApplicationDelegate protocol.
What prompts AppDelegate to initially
run it's ...
Because in your MainWindow.xib file you have connected (well the project wizard did the connection actually) the File's Owner (which is the UIApplication object)'s "delegate" outlet to the UIApplicationDelegate object in the the .xib file, and the class of the UIApplicationDelegate is set to your app's UIApplicationDelegate subclass.
And there's nothing magic about "MainWindow.xib", it could be called "Foo.xib", what's important is that the property in your Info.plist file called "Main nib file base name" is "MainWindow". Trying renaming MainWindow.xib to Foo.xib and changing the "Main nib file base name" in your Info.plist to "Foo" and you'll see it still works.
EDIT: more about RootController
Again, there's nothing magic about the so-called "RootController". This is just the name of the UIViewController subclass created for you by the Xcode new project wizard.
The wizard places code in the project for two classes: ProjectAppDelegate and ProjectViewController. The ProjectAppDelegate class contains two outlet members:
IBOutlet UIWindow *window;
IBOutlet ProjectViewController *viewController;
in the MainWindow.xib file, instances of both UIWindow and ProjectViewController are placed, and hooked up to the above outlets in ProjectAppDelegate.
What gets your stuff up on the screen is this code in your ProjectAppDelegate class:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// Add the view controller's view to the window and display.
[self.window addSubview:viewController.view];
[self.window makeKeyAndVisible];
return YES;
}
Again, nothing really magic about this: the project wizard created code that adds your "root" ViewController's view to the window's view, and makes the window visible. Your "root" view controller was create in the .xib file, and hooked up to the ProjectAppDelegate outlet.
It is very instructive to try to create an application entirely by yourself without using any of the files from the wizard. You'll learn a lot about how .xib files work and how they relate to code objects.
The starting point of iOS apps is always the main() function (thanks #bogatyr) which usually contains code similar to,
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
The last two parameters of UIApplicationMain are important and specify the principal class name, and the application delegate. If they are nil, then Info.plist will be looked up for the main window xib (usually MainWindow.xib).
// If nil is specified for principalClassName, the value for NSPrincipalClass
// from the Info.plist is used. If there is no NSPrincipalClass key specified, the
// UIApplication class is used. The delegate class will be instantiated
// using init.
.. UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);
It is not necessary to set the File Owner through xib, and they can be specified directly in this UIApplicationMain function.
principalClassName can be the string UIApplication or a subclass of UIApplication. Similarly delegateClassName can be directly specified in this method. The delegate class is instantiated using init as the docs say. Suppose we specify our delegate class - MyAppDelegate as a string,
UIApplicationMain(int argc, char *argv[], nil, #"MyAppDelegate");
First an instance of UIApplication is instantiated, which will then create the delegate class from this string using NSClassFromString I suppose.
Once delegateObject has been instantiated, and application is ready, this delegateObject will be informed using the delegate method, didFinishLaunchingWithOptions.
Class delegateClass = NSClassFromString(#"MyAppDelegate");
id <UIApplicationDelegate> delegateObject = [[delegateClass alloc] init];
// load whatever else is needed, then launch the app
// once everything is done, call the delegate object to
// notify app is launched
[delegateObject application:self didFinishLaunchingWithOptions:...];
This is how UIApplication would handle it programmatically, if no nib is used. Using a nib in the middle is not much different.
Since your AppDelegate is a delegate of UIApplication - it listens to all notifications that UIApplication class posts during it's lifecycle. didFinishLaunching notification is one of them and it causes your AppDelegate to call aforementioned method.
For Universal — iPhone + iPad — apps you can specify that different NIBs load on each platform, either in the target Info panel or by adding NSMainNibFile~ipad and NSMainNibFile~iphone keys to your Info.plist. Alternatively, you can add a MainWindow~ipad.xib NIB to your target, it will be loaded on the iPad instead of MainWindow.xib, based on the NSMainNibFile key in Info.plist.
If you need more control and customization for a Universal app you can load the starting NIB manually. The "Universal" project template has the boilerplate for this method, so the quickest way to get started using this technique is to just create a new iOS project with the Universal profile.
In the above examples the Main NIB File is set in Info.plist (target settings) so that you will already have a NIB loaded when your application delegate is invoked. Usually in this setup a MyAppDelegate object will also be archived in the NIB (with some IBOutlets) and the NIB's File's Owner will be set to UIApplication.
For a universal project to be able to accommodate two alternative layouts, the Main NIB File key is left out of Info.plist. Then it instantiates the application delegate object programmatically in UIApplicationMain:
#import "MYAppDelegate.h"
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([MYAppDelegate class]));
}
}
Then check your environment and settings and load the appropriate NIB in application:DidFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
_window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
_viewController = [[[MYViewController alloc] initWithNibName:#"MYViewController_iPhone" bundle:nil] autorelease];
} else {
_viewController = [[[MYViewController alloc] initWithNibName:#"MYViewController_iPad" bundle:nil] autorelease];
}
_window.rootViewController = _viewController;
[_window makeKeyAndVisible];
return YES;
}
- (void)dealloc {
[_window release];
[_viewController release];
[super dealloc];
}
The new step is to create a root MYViewController manually, loading the appropriate NIB. In this setup the File's Owner is your shiny new MYViewController rather than UIApplication. If you want, MYViewController can adopt much of what you may have been using your application delegate for - which is often to encapsulate the core model class of the app, act as a data source and delegate for the views and other things in the NIB.
So you're expected to have some root UIView in the NIB, and it should be hooked up to the view outlet of the File's Owner (MYViewController).
Note that MYViewController's NIB isn't actually loaded until the first time the MYViewController.view property is accessed. Only then will [MyViewController viewDidLoad] be called! The most likely time for this to occur is when you add it to the root window.
In the template code shown above the root UIWindow is instantiated by the app delegate, but there's no reason you couldn't include it in your NIB instead. If you choose to do this, be careful. If you set the rootViewController of the window in the NIB to the File's owner in that case, it will cause the controller's view to be added to the window when the window is activated. Be careful constructing that first NIB in any case.
The app delegate doesn't necessarily need to have a reference to your root UIWindow if you want MYViewController to manage it, but it may be cleaner overall to keep the root window out of your NIBs and manage it in the app delegate.
Outside of that (!) there's not much different from the single-platform approach.
MainWindow.xib is defined in your info.plist as the Main nib file base name. In your MainWindow.xib, you define the first controller that you want to load, in your case, RootViewController.
didFinishLaunchingWithOptions: is part of the UIApplicationDelegate protocol. This method (in iOS4.0+) is always known to be the first to be called when launching an application.