I’ve been working on a iPhone game (NineSpeed) on and off for a while – although recently it has been mostly off. So when I got an email from Apple last Wednesday, telling me to submit my game by Monday to get in the app store for launch, then this spurred me into some kind of action.
Of course I was not going to finish the game by Monday, as I still had a load of UI stuff to do, and it was getting annoying, as I’d done everything in OpenGL up to that point, and starting to bolt on the necessary iPhone OS stuff (flipping views, saving states, entering text) was a bit intimidating.
So it struck me I could kill two birds with one stone – I could write a simple app for launch that would include all the UI elements I was lacking in NineSpeed. I needed something simple that I could do in three days, but also something useful, so it would actually get published (although that did not turn out to be as big an issue as I thought it might). And doing this would also let me go through the submission process, which might raise some issues in advance of me doing it for an app I hoped people would buy.
So I decided to do a Scrabble word checker. It would just input a word and then tell you if it was good or bad to play in Scrabble. There would also be an options screen where the user could change dictionaries. The finshed app, “CheckWord” is now on the store:
http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=284938074&mt=8
STARTING FROM SCRATCH
iPhone development is done using two applications on the Mac: XCode and Interface Builder. These two apps are closely linked. XCode lets you create, edit, build and test code. Interface Builder lets you build pretty screens with buttons and suchlike, and link it up with the code you made in XCode.
XCode will make a basic iPhone application for you. Simple choose “New Project”, and you get to pick from a few basic templates. I chose “Utility Application”, which gives you two “views”, and a button that flips between them.
A “view”, as it turns out, is nothing complicated. It’s just a region of a window. A window is just the entire iPhone screen – they are resizable on the Mac, but on the iPhone you can only rotate them. It’s perhaps a little confusing.
First confusion is that there are actually three views: Root, Main and Flipside. There are also three “Root Controllers”, which are objects that control the views. The template sets all these up so that it displays the main view with the root view overlaid on top of it, and you can press a button to flip to the flipside view. So in theory all I had to do was add a text entry field and some text display fields to the main view, and hook them together with some code, and then add some options on the flipside screen.
FIDDLY
So that’s what I did. It’s bit fiddly, and for reference I used the “Hello World” sample app that inputs some text and then displays it on screen. So my app ended up being an amalgam of the two. I started with “Utility Application”, and then pasted in bits of “Hello World”
That worked reasonably well for the code, but of the Interface Builder (IB) stuff, I had to go in and visually check each element to see how it was connected. The connections between IB and the code are pretty simple. There’s an IBOutlet, which is just a data field that refers to the button or label, or whatever. Then there’s an IBAction, which is a function you define that is called when some event occurs on an IB object.
To actually hook these up, you just define them in the code, then IB will detect them, and when you right click on the appropiate object you get a list of outlets and actions and can drag them to the required place.
It’s not quite that simple though, and for a first-timer like myself, it can be rather confusing. You’ve got all your object, but you also have these other objects: the “File’s owner”, the “First Responder” and sometimes the “App Delegate”. The “File’s Owner” you have to hook up to the class of the correct object in your code. If this object is an UIApplication, then there’s a ‘delegate’ outlet, which you set to the App Delegate object, which is set to the class of that object. Then either the File’s Owner or its delegate will have the outlets, and the “First Reponder” will have the actions.
If that’s hard to understand, it’s because I don’t fully understand it, and so I can’t explain it very well. I’m still at a relativly early stage of learning this particular model of programming, and it will take a while before I get the nuances.
ACTUAL CODE
So all that’s just setting up connections between IB and the code. What about actual code for chcking words? Well, it turns out that that’s the easiest code to write. Not because it’s any simpler, but because I know EXACTLY what the code is doing, since it’s just some raw C++ whizzing though arrays of bytes and doing some comparisons, simple stuff like:
bool CWordList::IsValid(const char *p_word) { if (strlen(p_word) <= 1) return false; for (int i=0;i < m_words;i++) { if (strcasecmp(p_word, mp_words[i]) == 0) return true; } return false; }
Nothing complicated there, just some nice simple cowboy code :)
SUBMITTING
The process of submitting the app to the app store was actually fairly painless in comparison with some things I’ve had to do in the past. There were lots of instructions, and while there were a few problems, if you simply follow the instructions, all will be well. The one problem I had was that at some stage the distribution build had to actually be built before some signing thing could be changed, and then it had to be built again.
I packed it up, uploaded it with some screenshots, and a few hours later it was approved. Nice.
Anyway, in closing, here’s a list of the various discreet things I learned how to do, mostly for my own reference.
LOAD A BINARY FILE
Add the binary file to your “Resources” in xcode so it goes into the “bundle”. Then code to load “TWL06.txt” into some C++ style allocated memory is:
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"TWL06" ofType:@"txt"]; NSData *fileContents = [NSData dataWithContentsOfFile:filePath]; int len = fileContents.length; char *p_data = new char[len]; [fileContents getBytes:p_data];
TEXT INPUT
Create a UITextField object in IB, and one one the view controller’s interface, which you have to also decorate with UITextFieldDelegate, like:
@interface MyViewController : UIViewController { IBOutlet UITextField *textField; ... @property (nonatomic, retain) UITextField *textField;
You then override textFieldShouldReturn to call resignFirstResponder and return YES if you want the keyboard to go away after text entry. In my case I wanted to it stay, and I also check the word as soon as the text is entered, so I have:
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField { if (theTextField == textField) { //[textField resignFirstResponder]; [self checkWord:nil]; } return NO; }
SAVING STATE
NSUserDefaults is what you want, it stores a dictionary of objects. In applicationDidFinishLaunching, you check for the existence of it, and create a default if needed:
NSString *kDictionaryIndexKey = @"dictionaryIndexKey"; NSString *kAcceptableSwitchKey = @"acceptableSwitchKey"; NSString *kNameKey = @"nameKey"; ... NSString *testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kNameKey]; if (testValue == nil) { printf("Dictionary Index key not found, creating default defaults\n"); // since no default values have been set (i.e. no preferences file created), create it here NSString *name = @"CheckWord"; NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys: name,kNameKey, [NSNumber numberWithInt:0], kAcceptableSwitchKey, [NSNumber numberWithInt:0], kDictionaryIndexKey, nil]; [[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults]; [[NSUserDefaults standardUserDefaults] synchronize]; }
Then you read them like this:
[acceptableSwitch setOn:[[NSUserDefaults standardUserDefaults] integerForKey:kAcceptableSwitchKey]]; dictionary.selectedSegmentIndex = [[NSUserDefaults standardUserDefaults] integerForKey:kDictionaryIndexKey];
(That reading the options to set the switch the the multi-segment button used in the options screen)
And write them like this:
- (IBAction) changeDictionary:(id)sender { dictionaryIndex = dictionary.selectedSegmentIndex; [[NSUserDefaults standardUserDefaults] setInteger:dictionaryIndex forKey:kDictionaryIndexKey]; }
That’s an Action that takes the dictionaryIndex from the multi-segment button, and stores it in the user defaults.
That’s it, the options will be saved, and persist between sessions, and power-downs.
FLIPPING THE SCREEN
Just use the “Utility” template code.
MAKING THE ‘i’ LESS FIDDLY
This was an annoying one. The ‘i’ button in the sample worked fine in the simulator, but on the actual iPhone, it was very fiddly, and often seemed not to work for some reason. To fix this, I made a custom button, and used a screen-grabbed image of the ‘i’ instead. I made this 44×44 pixels with a lot of transparency (although I think you can just re-size any custom button, regardless of the bitmap). This improved things a lot.