ios labs tutorial

Getting Started on Your First iOS App – Part I

The app we’ll be building in this blog is a simple score keeping app. The requirements are simple: I want to be able to track my weekly Sheepshead game scores by opening up my ScoreKeeper app and writing out each player and their score for the day. I also want a running average of score per scoresheet to prove that I’m actually a decent player, I just keep losing over lunch.

The problem is, I don’t know any iOS. I searched our own blog stack and the intertubes of blogs and found compendiums of misdirection or guides assuming I already knew iOS. I know nothing. Quite literally, the square brackets scare the hell out of me. So I’m going to do what any good pivot would do when scared shitless: I’m gonna blog it. This will be a step-by-step of how to bootstrap, implement, and improve a “simple” iOS app.

Settings

So first thing first: what is my work station? At Labs we rotate between stations and due to a recent[1] Mavericks launch I am still on a Mountain Lion Mac (10.8.5). Similiarly, this station is running XCode 5.0.2.

Create and Configure the Project

In XCode, let’s start by creating an empty project. For template, choose iOS > Application > Empty Application. For Class Prefix, we’re leaving this blank. Apparently the convention is to do prefixes with libraries; for your app, no prefixes. We’re going for an iPhone app and with no Core Data, so leave that unchecked. After choosing a name, pick your location and ask XCode to create a git repository for you. Cool, now you have a completely empty iPhone app, congrats.

At Labs we dogfood a bunch of tools across many different platforms. For iOS one of those tools is Cedar. Because we’re bleeding edge developers, we’re going to be testing against the head version; because of this, we are going to check out Cedar as a submodule in our app. The first thing we need is an Externals/ directory at the top level of our application:

→ ll
total 0
drwxr-xr-x 6 pivotal staff 204 Apr 2 12:10 .
drwxr-xr-x 53 pivotal staff 1802 Apr 2 12:10 ..
drwxr-xr-x 13 pivotal staff 442 Apr 2 12:13 .git
drwxr-xr-x 9 pivotal staff 306 Apr 2 12:10 BlogScoreKeeper
drwxr-xr-x 5 pivotal staff 170 Apr 2 12:10 BlogScoreKeeper.xcodeproj
drwxr-xr-x 5 pivotal staff 170 Apr 2 12:10 BlogScoreKeeperTests

→ mkdir Externals

Now we clone Cedar as a submodule:

git submodule add https://github.com/pivotal/cedar.git Externals/cedar

In addition to Cedar, we cloned BetterConsole and CedarShortcuts to make our XCode lives better:

cd ~/workspace
git clone https://github.com/cppforlife/CedarShortcuts.git && cd CedarShortcuts && rake install
cd ~/workspace
git clone https://github.com/cppforlife/BetterConsole.git && cd BetterConsole && rake install

Now that we have some of our tools, let’s restart XCode. After opening up our empty app, we need to add iOS Cedar Spec Suite as a target.

01_AddTarget
Generally, we name the Spec Suite UISpecs or something similiar. After creating UISpecs, find and open the folder in the left nav. You will notice a Rakefile and a framework file; toss both of them:

02_Throway

Now, right click the top level entry in the left nav and click New Group. Give it the name of Externals. Open up the Cedar submodule in Finder and click and drag the xcodeproj file into your Externals folder in XCode. The end result

03_MoveCedarProj

Ready for some library surgery? Yeah, we know you are. Click on the top entry (your project) in the left nav. This should open a settings page for your app and UISpecs. Click on UISpecs so we can configure how UISpecs sees the world. At the top are four “tabs,” one of which is Build Phases. Clicking on Build Phases we can expand the top Target Dependencies menu and add a dependency with the + button. A menu drops down with items to add, choose Cedar-StaticLib. Now in the bottom menu, Link Binary With Libraries, add libCedar-StaticLib.a. Your page should look somewhat like this:

04_Link

In the Build Settings tab, scroll down about halfway to the Search Paths menu. Under the selection User Header Search Paths, you need to add Cedar’s headers, which should be under

/path/to/YOUR_PROJECT/Externals/cedar/Source/Headers

Switch the selection to be recursive.

06_UserHeaderPaths

In the same menu, Search Paths, delete any value you might have for Framework Search Paths (the technical reason is we’re using the Cedar submodule, not the Cedar framework).

Stay on Target

We’ve linked. Now we build and test. To do this, we need to switch to the UISpecs scheme. A scheme is a separate app using the same workspace; in this case, Cedar runs as a wholly separate app using our production code to throw against the specs. To switch to the UISpecs scheme, find the quasi-hidden dropdown in the top left next to the play/stop buttons. Click it and you should see a list of schemes; go ahead and click UISpecs:

05_SwitchScheme

Now that you’re in the UISpecs scheme, click the big play button. You should see errors in the left nav pane. If you don’t, click the icon that looks like an exclamation point in a triangle. Note there are three to four entries: Something about Retina 4-inch support (ignore it), something about <command line> (this is a downstream issue), and a file not found in both main.m and UISpecs-Prefix.pch. Since we are using Cedar as a submodule, we need to change the include syntax:

<<<<<<
#import <Cedar-iOS/SpecHelper.h>
======
#import "SpecHelper.h"
>>>>>>

and

<<<<<<
#import <Cedar-iOS/Cedar-iOS.h>
======
#import "Cedar-iOS.h"
>>>>>>

Now run your specs again by pressing the Play button. If you’ve followed the instructions here (and we’ve written this blog post well) you should see passing specs and an iOS Simulator window. Congrats, you’ve just bootstrapped your first test driven iOS app (PS. It still does nothing).

A Quick Test Case (to prove we’re not crazy)

So now that we’ve linked and compiled and included and submoduled we want to do what we enjoy doing: writing failing tests and making them go green. Really what we’re wanting to do is prove out that our wiring worked, so this test will be simple.

Let’s create a spec for AppDelegate, a freebe we get that is injected into the app’s main method as an entry point. In the left nav, click the folder to go back to the Project Navigator. Right click UISpecs > New File… > iOS > Cedar > Cedar Spec. The class we are going to test is AppDelegate. Hit next and make sure UISpecs is the only target, then click Create. Running the specs right now should show a pending test case, this is good.

What we want to test is that the AppDelegate wires up a RootViewController as the application’s root view. Time to copy paste stuff:

#import "AppDelegate.h"
#import "RootViewController.h"

using namespace Cedar::Matchers;
using namespace Cedar::Doubles;

SPEC_BEGIN(AppDelegateSpec)

describe(@"AppDelegate", ^{
    __block AppDelegate *subject;

    beforeEach(^{
        subject = [[AppDelegate alloc] init]; //stickshift

    });

    describe(@"after the app finishes launching", ^{
        beforeEach(^{
            [subject application:nil didFinishLaunchingWithOptions:nil];
        });

        it(@"should set the rootViewController to our RootViewController", ^{
            subject.window.rootViewController should be_instance_of([RootViewController class]);
        });
    });
});

SPEC_END

If we prematurely run our specs, we’ll see find not found for our newly referenced RootViewController. For now we can generate that and leave it be. In your app folder, right click > New File… > iOS > Cocoa Touch > Objective-C class. Name it RootViewController and have it be a subclass of UIViewController. Also check “With XIB for user interface” (we will use later), then make sure both the main project and UISpecs are targets.

Running the specs at this point gives us a handful of Linker Errors. What we’re missing is that AppDelegate.m, the auto-generated file that came with creating a new project, is not included in the UISpecs target. To add AppDelegate, thus making the compile gods happy, navigate to AppDelegate.m, open the right panel (the Utilities Panel), and click the icon that looks like it’s a file (File inspector). In the Target Membership section, make sure both your project and UISpecs are checked as targets. As a note, all of your app code should always be in both targets, while all of your spec code should be in only UISpecs.

07_IncludeTargets

Running the specs, we should get a failing test. Our check for the RootViewController is failing. Time to head off to AppDelegate and tell it to use our RootViewController.

#import "AppDelegate.h"
#import "RootViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.rootViewController = [[RootViewController alloc] init];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

You know what you should do right now? You should run the specs again. Go ahead, we’ll wait…

…surprise! Everything should be passing. You’ve just bootstrapped your own iOS app and added your own custom functionality to it.

In our next blog post we’ll cover some GUI behavioral code.


This blog was brought to you by the guidance of Can Berk Güder, and co-authored by Yulia Tolskaya. The full source can be found here.

[1] Recent in astronomical terms