Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / iPhone

'Hello World, Goodbye .XIB!' -- A .XIB'less Hello World Application

4.50/5 (4 votes)
14 Sep 2010CPOL5 min read 33.5K   373  
An article that creates a simple iPhone application that displays a graphic then cuts out the .XIB, and completes this just from the code.

Part ONE: Hello world!

I'm going to create a simple iPhone application that displays a graphic. Then I'm going to cut out the .XIB, and make it all happen from code.

I've written this article in a style so as to make it accessible for beginners, however some of the material is a little advanced. But life is a challenge...

Okay, let's get started. Fire up Xcode, create new project -> view based application, call it "XIB"

build & run [cmd + enter], and it should give a blank screen with the status bar going along the top.

Right click on ' classes ' from your groups & files pane -> add new file -> IPhone OS Objective-C class, call it 'myView'. It should automatically create a header as well.

Replace the text in these files with:

//  MyView.h

#import <UIKit/UIKit.h>

@interface MyView : UIView {
}

@end

//  MyView.m

#import "MyView.h"

@implementation MyView

/*
- (id)initWithFrame:(CGRect)frame 
{
    if (self = [super initWithFrame:frame]) 
    {
    }
    return self;
}
*/
 
- (void)drawRect:(CGRect)rect 
{
    NSLog(@"MyView:drawRect...");

    CGContextRef X = UIGraphicsGetCurrentContext();    
    
    CGRect bounds =  CGContextGetClipBoundingBox(X);
    CGPoint center = CGPointMake((bounds.size.width / 2), (bounds.size.height / 2));
    
    NSLog(@"--> (drawRect) bounds:'%@'", NSStringFromCGRect(bounds));
    
    // fill background rect dark blue
    CGContextSetRGBFillColor(X, 0,0,0.3, 1.0);
    CGContextFillRect(X, bounds);
    
    // circle
    CGContextSetRGBFillColor(X, 0,0,0.6, 1.0);
    CGContextFillEllipseInRect(X, bounds);
    
    // fat rounded-cap line from origin to center of view
    CGContextSetRGBStrokeColor(X, 0,0,1, 1.0);
    CGContextSetLineWidth(X, 30);    
    CGContextSetLineCap(X, kCGLineCapRound);
    CGContextBeginPath(X);
    CGContextMoveToPoint(X, 0,0);
    CGContextAddLineToPoint(X, center.x, center.y);
    CGContextStrokePath(X);
    
    // Draw the text NoXIB in red
    char* text = "Hello World!";
    CGContextSelectFont(X, "Helvetica Bold", 24.0f, kCGEncodingMacRoman);
    CGContextSetTextDrawingMode(X, kCGTextFill);
    CGContextSetRGBFillColor(X, 0.8f, 0.3f, 0.1f,  1.0f);    
    CGAffineTransform xform = CGAffineTransformMake(
                                                    1.0f,  0.0f,
                                                    0.0f, -1.0f,
                                                    0.0f,  0.0f   );
    CGContextSetTextMatrix(X, xform);    
    CGContextShowTextAtPoint(X, center.x, center.y, text, strlen(text));
}


- (void)dealloc {
    [super dealloc];
}


@end

Now double-click XIBViewController.xib to open it up in Interface builder. Set the view's class to myView -- you don't need to type -- it is in the pulldown.

setClass.png

[Cmd + s] [Cmd + q] [Cmd + enter] (save, quit out of IB and build+run) and you should get this:

Hello_World.png

This isn't a haphazard attempt at art. It's actually a very good visual tester:

  • The rectangle encompasses the entire viewing area.
  • The inscribed ellipse will let us instantly see if some of the viewing area is offscreen -- if it just touches the border on all four sides then all is good.
  • The line goes from (0, 0) to the centre of the view. There is a lot of legacy nonsense regarding coordinate systems (some start from the top, some start from the bottom), so you really need to know where is (0, 0).
  • I have capped the line at both ends. some views clip to bounds, some don't. This is a good way of finding out.

As if by magic! Yeah, but that's a bad thing. So how about we scrape out all the 'magic' and replace it with good old-fashioned code, so we can get some handle on what is going on.

Part TWO: Goodbye .XIB

If you don't know anything about .XIBs, we should mull a moment before axing them.

Imagine using IB -- dragging and clicking, to make buttons, menus, etc. So the XIB stores all this stuff a bit like this:

{ UIButton: Name = Bob, Color = red, rect = { 10,10, 50, 50 }, ... } 
{ ... } etc.

Back in your code, you do something like:

@property (bla) IBOutlet        UIButton *Bob;

...giving you a handle which you can use to play with your IB-created objects.

When you run your code, Xcode will take care of going through the .XIB, creating a UIButton initialised with Color = red, rect = {10, 10, 50, 50}, etc. and making your code's *Bob IBOutlet point to it, so your code can play with it directly.

Of course, that's an oversimplification. I don't really understand much more than that yet. Hopefully this project will help! Let's get started!

First Step: Delete Both .XIBs

Actually, even before you do this, start a new project called NoXIB and bring it up to this point. (Note: renaming projects can be a pain in the butt). I've also changed the colour from blue to red and the text from "Hello World!" to "Goodbye XIB!" in myView.m

Try building and running to experience a WTF moment. It still works!!! Yes -- it's a bug. When you delete a .xib, it fails to delete it in the simulator.

  1. Flush the simulator: [iPhone simulator -> reset content and settings]
  2. [Build -> clean all targets] (actually it's a double bug -- because this step is also needed)

Try again -- this time the app will crash as expected leaving you at the springboard. Good.

Next, load up the info.plist and remove the [key:value] pair [Main nib file base name: MainWindow]. Run again, and now no crash. It just black screens. Figures...

Next is a load of fiddling with code, so I recommend you download the project and play with it. I will list it here, for completeness -- it is not long. I have taken the liberty of moving main.m into the classes folder, which is just plain wrong, however -- it does present the top down order in which the classes invoke one another:

Classes.png

For the most part I will let the code speak for itself -- I have commented it generously. If you are new to this stuff I suggest [Alt + double click] to get help on any keyword, and setting a breakpoint at the start of every method before single stepping [Shift + Cmd + O] through the whole shebang. Just single stepping on its own won't work -- you could blythely single step over this line of code...

CGRect X = self.viewController.view.bounds;

Without realising that just the act of accessing the view controller's view object is capable of triggering its loadView and viewDidLoad methods (this only happens if the view is a null pointer).

Maybe one day the IDE will evolve to a point where single stepping takes callbacks into account. In the meantime, the breakpoint is your friend.

You can also bring up the console with [Shift+Cmd+R] -- I really needed this figuring out what was going on with the various view / window / view controller / bounds / etc rectangles. It's not straightforward.

In the settings.h file there is an option:

#define SHOW_SB NO

This template gives the option of showing the status bar. As I like zero clutter, I have got rid of the thing. But if anyone wants it, just switch that to a YES.

If you are new to these things and want to play straight away, I suggest sticking code in myView.m. You shouldn't need to touch anything above it for a while.

//
//  Settings.h
//  NoXIB
//
//  Created by pi on 27/08/2010.
//

/* This template lets you choose whether you wish to show the status bar.  
 On iOS 3+ there is a third option: to show it transparent, while allowing the  
 window to draw full-screen behind it. If you want this, you may need to fiddle 
 with self.viewController.wantsFullScreenLayout */
#define SHOW_SB NO



//
//  main.m
//  NoXIB
//
//  Created by pi on 13/08/2010.
//

#import <UIKit/UIKit.h>

int main(int argc, char *argv[]) 
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    int retVal = UIApplicationMain(argc, argv, nil, @"NoXIBAppDelegate");
    
    [pool release];
    
    return retVal;
}



//
//  NoXIBAppDelegate.h
//  NoXIB
//
//  Created by pi on 13/08/2010.
//

#import <UIKit/UIKit.h>

@class NoXIBViewController;

@interface NoXIBAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    NoXIBViewController *viewController;
}

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

@end




//
//  NoXIBAppDelegate.h
//  NoXIB
//
//  Created by pi on 13/08/2010.
//

#import <UIKit/UIKit.h>

@class NoXIBViewController;

@interface NoXIBAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    NoXIBViewController *viewController;
}

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

@end




//
//  NoXIBAppDelegate.m
//  NoXIB
//
//  Created by pi on 13/08/2010.
//

#import "NoXIBAppDelegate.h"
#import "NoXIBViewController.h"
#import "Settings.h"

@implementation NoXIBAppDelegate

@synthesize window;
@synthesize viewController;


- (BOOL)application:(UIApplication *)application 
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{    
    [[UIApplication sharedApplication] setStatusBarHidden:(SHOW_SB ? NO : YES)];

    CGRect appFrame = [UIScreen mainScreen].applicationFrame;
    
    // Create the window that will host the viewController
    
    // windowRect must start at 0, 0
    // if (SHOW_SB == YES), appFrame will be '{{0, 20}, {320, 460}}'
    CGRect windowRect = CGRectMake(0, 0, appFrame.size.width, appFrame.size.height);
    
    self.window = [[[UIWindow alloc] initWithFrame:windowRect] autorelease];    
    
    /* NOTE: init_withFrame lets the view controller know the dimensions of the
    view it must create. it will create the view when loadView is triggered. 
    This will occur automatically the first time viewController.view is accessed. 
    ie in [window addSubview:viewController.view]; below */
    self.viewController = 
        [[[NoXIBViewController alloc] init_withFrame : appFrame] autorelease];
    
    /* viewController.view: If you access this property and its value is currently nil, 
     the view controller automatically calls the loadView method and returns the
     resulting view. 
     The default loadView method attempts to load the view from the nib file 
     associated with the view controller (if any). If your view controller does
     not have an associated nib file, you should override the loadView 
     method and use it to create the root view and all of its subviews.*/
    
    // so can't do this: 
    //NSLog(@"    * viewController.view.bounds:'%@'", 
    //      NSStringFromCGRect(viewController.view.bounds)  );
    // or LoadView then viewDidLoad would get triggered
     
    // accessing a nil viewController.view forces loadView then viewDidLoad to be called
    [window addSubview:viewController.view];
    
    [window makeKeyAndVisible];
    
    return YES;
}


- (void)dealloc {
    [viewController release];
    [window release];
    [super dealloc];
}


@end



//
//  NoXIBViewController.h
//  NoXIB
//
//  Created by pi on 13/08/2010.
//

#import <UIKit/UIKit.h>

@interface NoXIBViewController : UIViewController 
{
    CGRect frame;
}

@property (nonatomic) CGRect frame;

- (id) init_withFrame : (CGRect) _frame;

@end




//
//  NoXIBViewController.m
//  NoXIB
//
//  Created by pi on 13/08/2010.
//

#import "NoXIBViewController.h"
#import "MyView.h"

@implementation NoXIBViewController

@synthesize frame;

- (id) init_withFrame : (CGRect) _frame
{
    frame = _frame;
    return [self init];
}

// Implement loadView to create a view hierarchy programmatically, without using a nib.
/* The view controller calls this method when the view property is requested but is 
 currently nil. 
 If you create your views manually, you must override this method and use it 
 to create your views. 
 
 If you use Interface Builder to create your views and initialize the view
 controller—that is, you initialize the view using the initWithNibName:bundle: method, 
 set the nibName and nibBundle properties directly, or create both your views and
 view controller in Interface Builder—then you must not override this method. */
- (void)loadView 
{        
    UIView* myV = [MyView alloc];
    
    [myV initWithFrame: frame];
    
    [self setView: myV];
    
    [myV release];
}

/* Discussion
 This method is called after the view controller has loaded its associated views
 into memory. 
 This method is called regardless of whether the views were stored in a nib file or 
 created programmatically in the loadView method. 
 
 This method is most commonly used to perform additional initialization steps on 
 views that are loaded from nib files. */
/*
- (void)viewDidLoad 
{
    [super viewDidLoad];
}
*/

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}


- (void)dealloc {
    [super dealloc];
}

@end



//
//  MyView.h
//  NoXIB
//
//  Created by pi on 13/08/2010.
//

#import <UIKit/UIKit.h>


@interface MyView : UIView {

}

@end



//
//  MyView.m
//  NoXIB
//
//  Created by pi on 13/08/2010.
//

#import "MyView.h"

@implementation MyView

/*
- (id)initWithFrame:(CGRect)frame 
{
    if (self = [super initWithFrame:frame]) 
    {
    }
    return self;
}
*/
 
- (void)drawRect:(CGRect)rect 
{
    CGContextRef X = UIGraphicsGetCurrentContext();    
    
    CGRect bounds =  CGContextGetClipBoundingBox(X);
    CGPoint center = CGPointMake((bounds.size.width / 2), (bounds.size.height / 2));
    
    NSLog(@"--> (drawRect) bounds:'%@'", NSStringFromCGRect(bounds));
    
    // fill background rect dark red
    CGContextSetRGBFillColor(X, 0.3,0,0, 1.0);
    CGContextFillRect(X, bounds);
    
    // circle
    CGContextSetRGBFillColor(X, 0.6,0,0, 1.0);
    CGContextFillEllipseInRect(X, bounds);
    
    // fat rounded-cap line from origin to center of view
    CGContextSetRGBStrokeColor(X, 1,0,0, 1.0);
    CGContextSetLineWidth(X, 30);    
    CGContextSetLineCap(X, kCGLineCapRound);
    CGContextBeginPath(X);
    CGContextMoveToPoint(X, 0,0);
    CGContextAddLineToPoint(X, center.x, center.y);
    CGContextStrokePath(X);
    
    // Draw the text NoXIB in light blue
    char* text = "Goodbye XIB!";
    CGContextSelectFont(X, "Helvetica Bold", 24.0f, kCGEncodingMacRoman);
    CGContextSetTextDrawingMode(X, kCGTextFill);
    CGContextSetRGBFillColor(X, 0.1f, 0.3f, 0.8f,  1.0f);    
    CGAffineTransform xform = CGAffineTransformMake(
                                                    1.0f,  0.0f,
                                                    0.0f, -1.0f,
                                                    0.0f,  0.0f   );
    CGContextSetTextMatrix(X, xform);    
    CGContextShowTextAtPoint(X, center.x, center.y, text, strlen(text));
}


- (void)dealloc {
    [super dealloc];
}


@end

Goodbye_XIB.png

... or just grab the zip:

Download NoXIB.zip - 16.91 KB

PS Code is a lot easier to understand with a decent colour scheme. Rather than simply try to get something looking pretty, I have clustered user defined objects into the red orange yellow spectrum with the standard objects in the blue purple end. Here's a screenshot of what I'm using:

ColorScheme.png

Feel free to stick it in ~/Library/Application Support/Xcode/Color Themes (you will probably need to create a folder) and Xcode -> preferences -> fonts and colours -> colour scheme should pick it up. It's worth looking here to see what the different colours are representing.

Download Pi.xccolortheme.zip - 946 B

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)