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:
#import <UIKit/UIKit.h>
@interface MyView : UIView {
}
@end
#import "MyView.h"
@implementation MyView
- (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));
CGContextSetRGBFillColor(X, 0,0,0.3, 1.0);
CGContextFillRect(X, bounds);
CGContextSetRGBFillColor(X, 0,0,0.6, 1.0);
CGContextFillEllipseInRect(X, bounds);
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);
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.
[Cmd + s] [Cmd + q] [Cmd + enter] (save, quit out of IB and build+run) and you should get this:
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.
- Flush the simulator: [iPhone simulator -> reset content and settings]
- [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:
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.
#define SHOW_SB NO
#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;
}
#import <UIKit/UIKit.h>
@class NoXIBViewController;
@interface NoXIBAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
NoXIBViewController *viewController;
}
@property (nonatomic, retain) UIWindow *window;
@property (nonatomic, retain) NoXIBViewController *viewController;
@end
#import <UIKit/UIKit.h>
@class NoXIBViewController;
@interface NoXIBAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
NoXIBViewController *viewController;
}
@property (nonatomic, retain) UIWindow *window;
@property (nonatomic, retain) NoXIBViewController *viewController;
@end
#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;
CGRect windowRect = CGRectMake(0, 0, appFrame.size.width, appFrame.size.height);
self.window = [[[UIWindow alloc] initWithFrame:windowRect] autorelease];
self.viewController =
[[[NoXIBViewController alloc] init_withFrame : appFrame] autorelease];
[window addSubview:viewController.view];
[window makeKeyAndVisible];
return YES;
}
- (void)dealloc {
[viewController release];
[window release];
[super dealloc];
}
@end
#import <UIKit/UIKit.h>
@interface NoXIBViewController : UIViewController
{
CGRect frame;
}
@property (nonatomic) CGRect frame;
- (id) init_withFrame : (CGRect) _frame;
@end
#import "NoXIBViewController.h"
#import "MyView.h"
@implementation NoXIBViewController
@synthesize frame;
- (id) init_withFrame : (CGRect) _frame
{
frame = _frame;
return [self init];
}
- (void)loadView
{
UIView* myV = [MyView alloc];
[myV initWithFrame: frame];
[self setView: myV];
[myV release];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
}
- (void)dealloc {
[super dealloc];
}
@end
#import <UIKit/UIKit.h>
@interface MyView : UIView {
}
@end
#import "MyView.h"
@implementation MyView
- (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));
CGContextSetRGBFillColor(X, 0.3,0,0, 1.0);
CGContextFillRect(X, bounds);
CGContextSetRGBFillColor(X, 0.6,0,0, 1.0);
CGContextFillEllipseInRect(X, bounds);
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);
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
... 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:
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