Friday, April 10, 2009

Tutorial: Analysing XCode's "Utility Application" iPhone-template in Interface Builder

In the last two posts we analysed XCode's "Window-Based Application" template, with an emphasis on understanding how to use Interface Builder (IB) during iPhone application development. That template is a good starting point, since it's very simply but yet makes rather good use of IB.


Now, we'll move onto something more exciting by analysing XCode's "Utility Application" template, so start up XCode, choose  "File/New Project" from the menu and select the Utility Application project and name it Util1. This is a quite interesting project, since it has multiple classes, multiple xib-files and even some user interaction via a GUI button. Lets start by looking at the file structure in XCode:


Util1

  Main View

    MainView.[hm]

    MainViewController.[hm]

  Flipside View

    FlipsideView.[hm]

    FlipsideViewController.[hm]

  Application Controllers

    RootViewController.[hm]

    Util1AppDelegate.[hm]

  Resources

    FlipSideView.xib

    MainView.xib

    MainWindow.xib

    Info.plist


Ok, it seems as if there are six classes (MainView, MainViewController, FlipsideView, FlipsideViewController, RootViewController and Util1AppDelegate). and three xib-files (FlipsideView, MainView and MainWindow). The things we recongnize from before are Util1AppDelegate, MainWindow.xib and Info.plist.


Before we dive into the details, build and run the project by pressing CMD-Return to see what it does! A grey background with a small icon in the lower right corner should appear. The small icon has a lowercase "i" in it, which in iPhone language means that it is an "info-button". Press it and see what happens!


Wow, it triggered a really nice graphical effect which "flipped" the grey screen away and flipped in a black screen with some kind of title bar containing the text "Util1" and a "Done"-button at the top of the screen. Let's press the "Done-button" and see what happens.


Cool, it flipped back to the original grey screen again! Ok, as you can see, this is a very nice application which probably would make anyone you showed it to drool in envy and bow in respect to you if you said that it was you who have made it!


After watching this fantastic presentation we understand why some of the files have names with the word "Flipside" in them - they probably have something to do with the stuff that was "flipped in" when we pressed the info-button on the main screen. Hey, there is a main screen also? Ok, now we understand why some files have the word "Main" in their names!  This flipping effect is actually designed to make it look like you can look at the backside of your main screen - almost like flipping your iPhone around in order to admire the beautiful Apple-logo ;)


Ok, enough talk, let's start wading through the files.


Examining the source files


Resources/Info.plist


Double-click it and you'll see that "Main nib file base name" is set to MainWindow, which means that Resources/MainWindow.xib will be loaded automatically when starting the application.


Before we start analysing the xib-file, we'll take a look at the classes, because otherwise it might be a bit hard understanding the objects and connectionsin the xib-file.


Application Controllers/Util1AppDelegate.h


@interface Util1AppDelegate : NSObject <UIApplicationDelegate> {


Ok, this is the object implementing the UIApplicationDelegate protocol...


    UIWindow *window;


and it has a window...


    RootViewController *rootViewController;


and something called a RootViewController. If you look in the file list in XCode, you'll see that this is one of the classes in our project so we'll dive into that one soon.


@property (nonatomic, retain) IBOutlet UIWindow *window;


We want to manipulate our GUI objects from IB so we declare accessor methods for 'window' and also tag it with IBOutlet so that IB will discover it.


@property (nonatomic, retain) IBOutlet RootViewController *rootViewController;


The same goes for the mystical 'rootViewController'.


Application Controllers/Util1AppDelegate.m


- (void)applicationDidFinishLaunching:(UIApplication *)application {


We want to do something when the application has finished launching so we implement the appropriate method from the UIApplicationDelegate protocol.

    

    [window addSubview:[rootViewController view]];


The mystical rootViewController has a reference to a UIView object, accessible by calling it's 'view' method. We want to add this view to our window in order to display it, which is accomplished by calling 'addSubView' on our UIWindow 'window'


    [window makeKeyAndVisible];


Calling makeKeyAndVisible on a UIWindow makes it visible as well as making it the "first responder" of events (touches).


Application Controllers/RootViewController.h


@interface RootViewController : UIViewController {


The mystical class RootViewController turns out to be a subclass of UIViewController. A UIViewController is primarly used if you have a full screen view, which we do, and using it instead of just a view has a couple of benefits, but we won't go into that now.


    UIButton *infoButton;

    MainViewController *mainViewController;

    FlipsideViewController *flipsideViewController;

    UINavigationBar *flipsideNavigationBar;


We have an UIButton called 'infoButton', two other view controllers and a navigation bar. Judging by their names the view controllers are used for the main screen and the flipside screen we saw when we tested the application.


}


@property (nonatomic, retain) IBOutlet UIButton *infoButton;

@property (nonatomic, retain) MainViewController *mainViewController;

@property (nonatomic, retain) UINavigationBar *flipsideNavigationBar;

@property (nonatomic, retain) FlipsideViewController *flipsideViewController;


We want all of our properties to be accessible by other classes so we declare them as properties, but we only mark 'infoButton' with IBOutlet so apparently that's the only thing we will manipulate from IB.


- (IBAction)toggleView;


IBAction instead of IBOutlet - that's something new! You'll have to read the full post before understanding this one, so for now you can ignore it.


Main View/MainViewController.h

Flipside View/FlipsideViewController.h


Both these just declare that they are subclasses of UIViewController.


MainView/MainView.h

FlipsideView/FlipsideView.h


Both these just declare that they are subclasses of UIView.


MainView/MainView.m

FlipsideView/FlipsideView.m


Both these are quite unexciting, but the following might be worth explaining


- (id)initWithFrame:(CGRect)frame {


Initialising this view is done by passing it a CGRect which defines a rectangle called 'frame', which will be used to set the size of the view. The return type is 'id' since we will return an object - a MainView or FlipsideView object to be precise.


    if (self = [super initWithFrame:frame]) {


The CGRect 'frame' is immediately passed to it's superclass UIView to take advantage of the initialisation it already provides. If the UIView initialisation worked we'll get a non-null pointer to an UIView in 'self' - that's "us".


    return self;


Here we return a pointer to "ourselves" so our caller can use the object we now have successfully initialised.


Main View/MainViewController.m


- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {


Creating a MainViewController object will apparently be done by providing a nib (IB-file).


    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {


This nib is directly passed on to our superclass UIViewController to let that handle the initialisation.


Flipside View/FlipSideViewController.m


This class has no "init"-method like MainViewController. Instead it has a method called viewDidLoad. This method is implemented in UIViewController (our superclass) and we should override it, as we do here, if we want to change something after our view has been loaded from a nib.


Since we implement this method, it means that this view will be loaded from a nib from somewhere else in our application. 


- (void)viewDidLoad {

    [super viewDidLoad];

    self.view.backgroundColor = [UIColor viewFlipsideBackgroundColor];      

}


We apparently want to change the background colour after loading, but to make everything work as intended we also call the 'viewDidLoad' method of our superclass to take advantage of the functionality it provides.


Examining of the xib-files


Finally, we have looked at the basic funcionality of all the classes in our project. Let's move on to the xib-files!


Resources/MainWindow.xib


File's owner


CMD-4 reveals that the object which is going to use ("own") this file is of the class "UIApplication", just like we've seen before. As we know by now, UIApplication objects has a delegate object which implements the UIApplicationDelegate protocol. Specifying the delegate object can be done fron IB by setting the 'delegate' outlet of an UIApplication object. CMD-2 reveals that this object's delegate outlet is set to "Util1 App Delegate".


Util1 App Delegate


CMD-4 shows that the class of this object is "Util1AppDelegate". It also shows that this class has two outlets; 'rootViewController' which is of type "RootViewController" (one of the classes we defined ourselves in the project) and 'window' of class UIWindow. CMD-2 shows that 'rootViewController' is connected to "Root View Controller" and 'window' is connected to "Window".


Root View Controller


CMD-4 gives us that this object is of the class "RootViewController" (one of our classes). and that it has an outlet called 'infoButton' of type UIButton. It also shows us something we haven't seen before - an "action" called 'toggleView' of type id. If you have forgotten where this come from, take a look at "Application Controllers/RootViewController.h" in XCode by pressing the small right-arrow icon in the title bar above the 'toggleView' row in the "Class actions" box in the Identity Inspector (CMD-4).


This is getting exciting, so let's have a look at the connections right away! Press CMD-2 and you'll see that 'infoButton' is connected to something called "Light Info Button" and 'toggleView' is connected to "Light Info Button Touch Up Inside". You might have noticed that this object apparently also has an outlet called 'view' which is connected to something called "View". These are here because "RootViewController" is a subclass of "UIViewController" and that's where the 'view' outlet comes from. In fact, UIViewController has two other outlets as well; 'navigationItem' and 'tabBarItem'. Take yet another look at the "Root View Controller" connections (CMD-2) and you'll find those two there as well, but not connected to anything.


By the way, if you want to verify that these outlets really come from UIViewController, bring up the Library (CMD-L) and drop a "View Controller" in the MainWindow.xib window. Select it and press CMD-4 and you'll see all the outlets mentioned above . When you're done verifying, select the "View Controller" icon and press backspace to get rid of it.


A short note on the section "Referencing outlets". Select "Util1 App Delegate" and press CMD-4. In the "Referencing outlet" section you'll see a "connection" that looks like this: 'delegate' -- 'File's owner". What this means is that the outlet 'delegate' in the object 'File's owner' points to this object. If you read it from right to left it becomes even clearer: "File's owner".delegate = "this object".


Ok, lets leave the generic UIViewController and return to our view controller. As mentioned above the 'view' outlet is connected to an object called "View", but how come we see no icon for it in the xib-file contents window ("MainWindow.xib"). That's because you can embed objects in other objects in IB and that is exactly what the author of this xib-file has done.


To reveal these embedded objects, we have to change the view mode of the contents window, which you do by pressing one of the small icons in the upper left part of the window - just above the text "View mode". Press the middle one and you'll be presented with a hierarchical navigation view mode (tree mode). Click the arrow to the left of "Root View Controller" and the mystical "View" object should appear. Press the arrow to the left of that one and the even more mystical object called "Light Info Button" should appear. If you are really really observant you could also find these - or at least the info button - by double clicking the "Root View Controller" icon in the "normal" view mode of the contents window. (Hint: it's in the lower right corner of the window that appears if you try it...)


By they way, if you wonder about how to embed an object into another object you do that by dragging the object you want to embed onto the other object. That's how View was put into "Root View Controller". For buttons and elements where the position is important, you can also position them at the same time as you embed them. Try that by double-clicking "View" in the tree view. This should bring up a "blank window". The info button already present is barely visible in the lower right corner. Bring up the Library (CMD-L) and drag a "Segmented Control" from the "Inputs & Values" section into the blank view. It should immediately appear and you can position it and size it at will. Check the tree view of the contents window to confirm that it appeared under View in the tree. Remove it by selecting it and pressing backspace, when you're done playing.


View


When you are in the "tree mode" it becomes easy to select the View icon so do that and press CMD-4 and then CMD-2. Nothing exciting, it's a simple UIView object with no outlets and no connections.


Light Info Button


In tree mode it's just as easy to select the "Light Info Button" icon and press CMD-4. Hey, not even this was that exciting - it's just a simple "UIButton"! Ok, maybe CMD-2 will make things a bit more fun? Wow, look at the list of... "stuff" that appears! If you look closer you'll see that the "stuff" actually are called "Events" and apparently we have done something with one of them - "Touch Up Inside". It looks like this event is "connected" to "Root View Controller toggleView". Sounds familiar? It should, because we mentioned it above. We earlier saw that we defined an IBAction called 'toggleView' in RootViewController.h, something which you can also see by selecting the "Root View Controller" icon here in IB and pressing CMD-2. Cool! It thus seems as if "events" are connected to "actions". Sounds reasonable!


Let's dig deeper into this. If you bring up XCode and do a project-search for 'toggleView' by pressing CMD-F you'll see that it is defined in RootViewController.h simply as -(IBAction)toggleView and then it is implemented as a method in RootViewController.h using the same "signature". Could it be that this method gets called when the info button is pressed. Yes, that's right! Or actually it gets called when the "Touch Up Inside" event is generated by the button, which means that you have to touch the button ("touch down") and remove your finger ("touch up") while the finger is still "inside" (hoovering over) the button.


One mystery remains for the infoButton. How did the iPhone know that we wanted a button which looked liked a the letter "i" inside a ring? Press CMD-1 to find out. Here you'll see that the type is specified as "Info Light" and that explains that mystery!


Window


Double click the Window icon and a black window appears. CMD-4 reveals that it is a plain UIWindow and CMD-1 confirms that the background colour is set to black.


Resources/MainView.xib


File's owner


CMD-4 shows us something new. The "File's owner" objects we have worked with so far have all been UIApplications, but this one is of the class "MainViewController", which is one of our own classes. We see that it has no outlets of its own, but if we switch to the connections tab (CMD-1) we see that the three outlets inherited from its superclass UIViewController are there; 'navigationItem', 'tabBarItem' and 'view'. The 'view' outlet of the superclass is connected to something called "Main View" and if you take a look at the xib-file contents window (MainView.xib), you'll see where "Main View" came from - yes there is an object in this xib called just that.


Main View


CMD-4 says this object is of class "MainView" and that it has no outlets of its own. CMD-2 shows that there are no inherited outlets as well. However, we see that there exists a connection via a "Referencing outlet". Using the syntax from above, this means that "File's owner".view = "this object". In our case "File's owner" is of the class "MainViewController", which in its turn is of the class "UIViewController", which in its turn has an outlet called 'view and that's the outlet that this object is connected to.


Resources/FlipsideView.xib


File's owner


CMD-4 shows that the class of the object is "FlipsideViewController" and that it has no outlets of its own. CMD-2 shows that the standard UIViewController outlets are there and that one of them, 'view', is connected to "Flipside View"


Flipside View


CMD-4 gives us a class of "FlipsideView" and no outlets. CMD-2 shows a "referencing outlet" meaning that "File's owner".view = "this object".



Summary


Wow, what a ride! We have learned a lot this time, but even after digging through so much one mystery remains and that is how the "title bar" and "Done"-button on the "flipside" was created since we saw no trace of them in Interface Builder, not even after learning about the "tree view" of the xib-file contents window. Well, that's beacuase those GUI elements were created programmatically, that is by writing Objective-C source code, but we won't analyse that here since we're now trying to focus on the IB. If you're curious about how that works I suggest you go back to XCode and do a project search (CMD-F and remember to enable "ignore case") for "navigation". Hmm, that sounded like an excercise for the reader to me ;) Good luck!


3 comments:

  1. Thanks, Henrik! This was a very timely post to find. I am curious, however, about your environment. I created this project with the latest OS 3.0 Beta 3 SDK, and I did not get RootViewController.[hm] files. Can you tell me what version of the SDK you are using?

    Keep up these great tutorials!

    - Jack
    Cleveland, OH, USA

    ReplyDelete
  2. Hi Jack! Glad to hear that you found the tutorial helpful, but sad to hear that it didn't quite work for you in SDK 3.0! I still haven't taken the step up to SDK 3.0 so I'm still using the "old" SDK 2.2, which includes XCode 3.1.2 and Interface Builder 3.1.2.

    I'll start specifying the SDK version I'm using to avoid confusion in the future and I guess it's also time to make the switch to 3.0 soon and when I do I'll probably write a post explaining the differences.

    ReplyDelete
  3. This is the great post. I like all those thing which help me to enhance my knowledge. And this will also help to enhancing my abilities.
    Web Design Quote

    ReplyDelete