iOS Hello World With No Interface Builder or Storyboard

Inspired by Julia Evans who I’ve been following awhile and unabashedly posts about things she’s just learning, I’m going to start posting about some stuff I’m just learning! I might not know it all yet and might make mistakes!! This is exciting!!!

Anyway, right now I am trying to build a pretty simple iOS application. The app I’m aiming for is one I could probably build as a web app (in html/css/javascript) pretty fast, but learning Xcode, Objective-C and the entire toolchain seems like a useful thing to learn. I also don’t have a lot of time to really dig in so my time is fractured so starting with a “simple” app seems smart. Anyway, I first tried to use Xcode’s Storyboard and Interface Builder. Storyboard and Interface Builder are both graphical tools built into Xcode (Apple’s IDE for iOS & Mac development) that manage some xml configuration files that under the covers define the views and controls in your screens. I found this kind of infuriating. I have done UI work before so the concepts are not completely strange to me (I’ve done: web programming, client and server, a pebble app, and Windows MFC and GTK apps a long time ago). The problem with Storyboard was that immediately I didn’t understand how what I did in the GUI changed what appeared when I tried running my app. So really I need a map and guides to let me dig in without hiding things that will infuriate me. Maybe later I can use Storyboard.

It turns out, though, that people really want you to use Storyboard and Interface Builder. There don’t seem to be official samples that don’t use them (I picked half a dozen simple looking samples and they all seemed to have .xib or Storyboard files). After much google search term massaging (many of my searches turned up guides that assumed Interface Builder or Storyboard), I turned up a few posts that got me started. The first one got me some basic code so I could get grounded. The second was a great overview “map” of where I was going. The third told me how to remove the generated Storyboard (the new app templates with current Xcode always include a Storyboard).

A little while later, I had “Hello World” without using the GUI tools to construct the layout (and here I am a week or so later posting about it!) Since I had so much trouble finding what I needed, I thought I’d re-write it here. It’s also a form of rubber-duck learning: while I explain this into my editor (to post on the web), I’ll understand it better! On to the steps! First get an empty-ish project setup:

  1. Run Xcode (this is assuming you have it installed which I won’t go into).
  2. Create a new project. Pick Single View Application. Name it whatever.
  3. Delete the Storyboard file. Do this by clicking on it, then pressing the delete key.
  4. In “Supporting Files”, open Info.plist. Highlight the line that mentions the Storyboard.

Now we can go add code! Let’s look at our files. First, because we’re learning, let’s look at where “main” is (what will run first):

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

I already had to go look up some stuff! Even though I know C and C++, I’ve never really looked at Objective-C before. One thing is Objective-C uses “@something” all over for various directives. Objective-C is clearly a stitched together monster of a language but I kind of like that. I assume @autoreleasepool is for some kind of memory management so I’m not worrying about it just yet. But NSStringFromClass([AppDelegate class]) …. huh. The other examples just had text like @"AppDelegate". What’s the difference? The @"AppDelegate" version is just constructing an NSString (versus a bare null-terminated string) with a particular class name. Turns out I want the latter way because it’s interrogating a class to get its name (call class on AppDelegate and construct an NSString from it). This means if I renamed AppDelegate to something else, I’d get a build error rather than a runtime error).

All this is doing is constructing some main framework app (I’d guess with an embedded event loop) that delegates a lot of work to my code. So what does AppDelegate do?

AppDelegate header file:

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@end

The AppDelegate implementation file:

#import "AppDelegate.h"
#import "ViewController.h"

@interface AppDelegate ()
@end

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
}
- (void)applicationWillTerminate:(UIApplication *)application {
}
@end

The examples I was looking at wanted to save off a reference to the window and the main controller, so I modified these two files. AppDelegate header:

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UIViewController *rootController;
@end

The modified AppDelegate implementation:

#import "AppDelegate.h"
#import "ViewController.h"

@interface AppDelegate ()
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.rootController = [[ViewController alloc] init];

    [self.window addSubview:self.rootController.view];
    [self.window setRootViewController:self.rootController];
    
    [self.window makeKeyAndVisible];

    return YES;
}
// left out all the unmodified functions ...
@end

A lot to unpack here! First there’s a window created using the frame (bounding box) generated by asking the UIScreen class for the mainScreen and getting its bounds. The rootController is set with an instance of ViewController which is another class generated by Xcode for the simple project. Then, the window is told what view it has inside it and what its root view controller is. That second line I didn’t have initially and while the code “ran” I got an error and it wasn’t quite working right (the simulator didn’t show the simulated top status bar with time, battery, etc. in it). Clearly this matters somehow, although not critically. I’ll figure it out later! Finally, the ViewController. Its header I didn’t need to modify:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
@end

The original version of this file didn’t have a lot going on:

#import "ViewController.h"

@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

The default template assumes I’ll be using ‘nibs’ and Storyboard (nibs are files that reflect how you construct the UI in the Interface Builder to define your views). But I want to define the view myself in code. So I needed to modify it to support this:

#import "ViewController.h"

@interface ViewController ()
@end
@implementation ViewController
- (void)loadView {
    CGRect rect = [UIScreen mainScreen].applicationFrame;
    self.view = [[UIView alloc] initWithFrame:rect];
    self.view.backgroundColor = [UIColor whiteColor];
    
    UILabel *labelView = [[UILabel alloc] initWithFrame:CGRectMake(5,5,200,50)];
    labelView.text = @"Hello world!";
    labelView.textColor = [UIColor blackColor];
    [self.view addSubview:labelView];
}
// leaving out -viewDidLoad and -didReceiveMemoryWarning
@end

This constructs a really basic view that uses the entire space, sets the background color to white, then puts a label showing “Hello world!” on the screen. Looking this over while I type this up, I notice that in two places I’m asking for [UIScreen mainScreen] and doing something with it. Clearly this is probably not quite right and I’ll have to figure that out. But I have a working “hello world!” What did I learn?

  • Lots of basic Objective-C: how strings and properties are created, interfaces, implementation, sending messages (i.e. calling methods), etc.
  • Various UIKit APIs.
  • How to navigate the UIKit docs so I could figure out how to use UILabel and set colors.

And now, hopefully when someone searches the web for “iOS app hello world programmatic view” or something like that they find this useful!

Comments