Forms in an iPhone App

June 16th, 2009

Sometimes in an app, you need a way for a user to enter information, perhaps to signup for a service. A traditional form will work fine here - however, forms are pretty difficult to “get right” in a native iPhone application. You could just throw a basic HTML form together and then embed it in a webview, but that’s not quite as clean as using native classes like UITextField, etc. In this post I will show you how to polish forms, and text entry in general, on an iPhone.

The easiest way to start off is to just open IB and throw together an interface. I’m not a huge fan of how native text fields look on the iPhone, so I’m going to draw up a background that already has text field borders on it in Photoshop. Something like this:

Now, I’m going to go into IB and throw it on the screen inside a UIImageView, and then put some unbordered UITextFields on top of it (make sure they fit nicely). That way, my text fields look sleek and custom, and I don’t have to deal with things like alignment and overflow (the unbordered UITextField takes care of these). The result:

Okay, so you’ve got your form put together and the text fields added - now it is time to look at the text field options in IB:

Set these up how you want. I’m going to turn Capitalization and Correction off for username and password, and I’m going to mark the password field as “secure” (this masks the password with dots). I’m also going to change the keyboard type for the phone number field - for now, I’ll just have users enter their number as one long string, for example “7901231234.” Finally, I’m going to set the return key for the username and password fields as Next, and I’ll leave it as Go for the phone number (that way once they reach the final field, the return key will say Go and will allow them to submit).

So we fire up the app, tap on a text field, and instantly run into a problem: when the keyboard slides up, it covers the other two fields.

What a drag. This issue has to be solved manually in code, I usually like to implement something like this (inside of your custom UIViewController subclass):

@interface FormViewController : UIViewController  {
	IBOutlet UITextField *userField, *passField, *phoneField;
}
@end

@implementation FormViewController

- (void)viewDidLoad {
	userField.delegate = self;
	passField.delegate = self;
	phoneField.delegate = self;
}

- (void)textFieldDidBeginEditing:(UITextField *)textField {
	[UIView beginAnimations:@"KeyboardSlide" context:nil];
	if (textField == userField) {
		self.view.frame = CGRectMake(0, -100, 320, 460);
	} else if (textField == passField) {
		self.view.frame = CGRectMake(0, -150, 320, 460);
	} else if (textField == phoneField) {
		self.view.frame = CGRectMake(0, -180, 320, 460);
	}
	[UIView setAnimationDuration:0.3];
	[UIView commitAnimations];
}

@end


That chunk of code sets up the UIViewController subclass as a delegate to the text fields, and slides the view up as the keyboard comes up with an animated effect which looks quite nice.

Now that we’ve tackled that problem, another one appears: what if the user wants to dismiss the keyboard? Sure, we could make each text field have a return button that dismisses the keyboard, but we really want the user to be able to use that return button to tab between fields. There is another more straightforward and logical way to dismiss: simply tap the view (above the keyboard), and the keyboard slides back down. A couple of builtin apps, including Safari, exhibit this behavior, and it is often the first thing a user tries when he wants to dismiss the keyboard after accidentally tapping a text field. So, again, in your UIViewController subclass, enter this code:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
	if ([userField canResignFirstResponder])
		[userField resignFirstResponder];
	if ([passField canResignFirstResponder])
		[passField resignFirstResponder];
	if ([phoneField canResignFirstResponder])
		[phoneField resignFirstResponder];

	[UIView beginAnimations:@"KeyboardSlide" context:nil];
	self.view.frame = CGRectMake(0, 20, 320, 460);
	[UIView setAnimationDuration:0.3];
	[UIView commitAnimations];
}

This function slides the keyboard back down ([textField resignFirstResponder]), and then slides the view back down to its starting point. One neat thing about UIViewController is that it exists in the responder chain - meaning touches that go to its associated view are also bounced off of the view’s controller. That’s how we can implement touchesEnded: in a UIViewController subclass without having to create a UIView subclass as well.

Okay, so our form is looking pretty good: the page slides up when tapped, and the user can dismiss the keyboard by tapping the page again. What else can we add? Well, the ability to tab between textfields with the Next button is not implemented yet, let’s tackle that.

Since we already established the UIViewController subclass as the delegate of the text fields, we can implement whatever delegate functions we want. So without further ado:

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
	if (textField == userField) {
		[passField becomeFirstResponder];
	} else if (textField == passField) {
		[phoneField becomeFirstResponder];
	} else if (textField == phoneField) {
		[self submitDetails];
	}
	return YES;
}

This function, upon receiving word that the return key has been pressed, passes the keyboard focus to the next text field. If the current focus is on the last (phone) field (which, remember, we setup to have an Enter key), the app submits the details just entered through your custom -(void)submitDetails; method.

What else is there to do? Well, since this form is a make-believe frontend to signing up for a web service, we probably want to impose limitations on the length and types of characters that go into these fields. Lets say that a username and password must be less than 16 characters each, and can contain only basic ASCII characters. The phone number must be exactly 10 characters.

One way to approach this is to simply check the contents of each field whenever the form is submitted and display a popup UIAlertView if one of the fields violates these rules. However, a better way to enforce these rules is by making it so that the user physically cannot violate them. Here’s how:

- (BOOL)containsUnicode:(NSString *)str {
	for (int i = 0; i < [str length]; i++) {
		unichar c = [str characterAtIndex:i];
		if (c > 127)	return YES;
	}
	return NO;
}

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
	if (textField == userField) {
		if (textField.text.length >= 16 && range.length == 0) {
			return NO;
		}
		if ([self containsUnicode:string]) {
			return NO;
		}
	} else if (textField == passField) {
		if (textField.text.length >= 16 && range.length == 0) {
			return NO;
		}
		if ([self containsUnicode:string]) {
			return NO;
		}
	} else if (textField == phoneField) {
		if (textField.text.length >= 10 && range.length == 0) {
			return NO;
		}

	}
	return YES;
}


The shouldChangeCharactersInRange: function is called anytime the user adds or removes a character in a text field. You can veto this change by analyzing it and returning NO from the function. To analyze, our code checks the current length, and makes sure that the new change does not contain any unicode characters.

So what can be improved here? The phone field is pretty lame - a string of ten straight numbers is going to be pretty hard for the user to read. We could try dynamically inserting the appropriate punctuation, but that would be pretty tough. Instead, let’s redo the design to incorporate three separate text fields for the phone number, each holding a different section. I came up with:

While this looks nice and is much more readable, it’s going to be a hassle to have to tap “Next” after entering just three numbers. Let’s have the fields change automatically… We’ll add a few more IBOutlets to our UIViewController (IBOutlet UITextField *phoneField1, *phoneField2, *phoneField3;) and set ourself to act as delegate to these fields (we can update the other delegate methods to reflect the new fields). Then, we’ll setup a UIResponder action that will be called after a character is entered:

- (void)viewDidLoad {
	userField.delegate = self;
	passField.delegate = self;
	phoneField1.delegate = phoneField2.delegate = phoneField3.delegate = self;
	[phoneField1 addTarget:self action:@selector(characterEntered:) forControlEvents:UIControlEventEditingChanged];
	[phoneField2 addTarget:self action:@selector(characterEntered:) forControlEvents:UIControlEventEditingChanged];
}

- (void)characterEntered:(id)sender {
	if (sender == phoneField1) {
		if (phoneField1.text && [phoneField1.text length] == 3) {
			[phoneField2 becomeFirstResponder];
		}
	} else if (sender == phoneField2) {
		if (phoneField2.text && [phoneField2.text length] == 3) {
			[phoneField3 becomeFirstResponder];
		}
	}
}

Why did we have to go through the hassle of setting up a target/action that is called whenever the field is edited? Can’t we just use the textField:shouldChangeCharactersInRange:replacementString: method like before? Actually, we cant - the shouldChangeCharactersInRange: method is called *before* the text field’s content is modified (this is what allows us to veto the change). If we were to switch to the next field in that method, the character would actually be added to the newly focused field - we definitely don’t want that. Instead, we signup to receive a callback form the UIControlEventEditingChanged event - the function is called *after* the text field has changed, so we can then safely swap the focus.

And that’s about it for forms. They’re a little bit tricky, and there are some obscure functions and events, but hopefully this post will help clear up forms for most people. If you want the source to the example project I was working on, you can download it here. For questions and suggestions, feel free to use the comments section.

More to come as always
Joe

Being Published

June 6th, 2009

Close to a year ago (I don’t remember exactly when), Damien Stolarz contacted me for permission to include an article that I had written in a book about iPhone hacks. Needless to say, I was very excited and happily complied (they even gave me a $50 contributor’s payment). The book has finally been published, and I went and grabbed a copy from Borders - it was pretty awesome walking into a major bookstore, pulling a book off the shelf, and seeing my name there in print.

I’m trying to keep this post from being a complete plug but it is difficult for obvious reasons :P. My contribution to the book came in the form of a blog post I wrote about iFlash - a useful little device I hacked together one day that serves as an external flashlight/camera flash for the iPhone. It was a very basic hack, but they really liked the concept, and next thing I know, my article and how-to are printed in a book.

The book turned out to be a really solid resource - it is an O’Reilly book after all (the big name in technology publishers), and I am very pleased with how it turned out. The book is called iPhone Hacks, and looks like this:

It is somewhere around 400 pages, and can be had for the low low price of $24 on Amazon.com.

My bio as it appears in the book:

Joe Vennix is a teenager from Houston, Texas, who has been hacking away at the iPhone since its launch. He sells apps on the iTunes store and maintains an iPhone news and apps listings site at www.iphonexe.com. His musical tastes vary from Pink Floyd to Buckethead to Funkadelic, and he’s pretty psyched about being published in a book. Shoot him an email via joe@iphonexe.com.

Awesome.

Age 17: first work published. What’s next? That’s what I’m trying to figure out.

Joe

charmap Update

June 5th, 2009

A big thanks goes out to Dave - he found my charmap utility and expanded on it, adding wrappable rows (to avoid the 1024px width limit on the iPhone) and customizable color functionality to the app. You now simply call the program like this:


$ charmap a-z Arial 15.0 15 ff0000

And the program spits out an image like this:

Which you can throw into a cocos project and use as a character map! Source can be found here and a prebuilt binary (osx 10.5 required) is available here.

Thanks again Dave,
Joe

JVBook.m - Easily add contacts to the iPhone’s AddressBook

May 10th, 2009

So after that last post I figured I’d write a little class that wraps around Apple’s AddressBook functions and makes adding a new contact a snap. I came up with JVBook, a framework that has two simple methods:

+ (JVBook *)sharedBook;
- (BOOL)addPerson:(NSDictionary *)args;


Simply grab the shared instance with the first method, and then call addPerson on it and give it a dictionary full of attributes (the attributes are defined in the JVBook.h file). Here’s some sample usage:

[book addPerson:[NSDictionary dictionaryWithObjectsAndKeys:@"Joe", JVPersonFirstName,
	 @"Michael", JVPersonMiddleName, @"Scott", JVPersonLastName,
	 @"1-822-212-6393", JVPersonMobilePhoneNumber,
	 @"5435 Waterfront Dr.", JVPersonHomeStreetAddress, @"90120", JVPersonHomeZip,
	 @"Los Angeles", JVPersonHomeCity, @"California", JVPersonHomeState,
	 @"isasdx@mac.com", JVPersonAIMScreenName, @"idasdax@gmail.com", JVPersonEmailAddress,
	 @"http://silentmac.com", JVPersonHomePageURL, nil]];


That’s all for today, hopefully someone finds that useful.
-Joe

iPhone SDK Tip: Adding a Contact to the Address Book

May 9th, 2009

Right now, adding a contact to the address book is a pretty big hassle. You have to drop down to core level API’s and use some poorly undocumented properties and keys with a dictionary to correctly add everything. For the sake of clarification I figured I’d throw up some sample code that shows you how to add a contact to the address book:

NSString *name = @"Michael Conor", *address = @"1234 Main St.",
*city = @"Kansas City", *state = @"Kansas";

//create the shared address book and the contact you're setting up
ABAddressBookRef ab = ABAddressBookCreate();
ABRecordRef person = ABPersonCreate();
ABRecordSetValue(person, kABPersonFirstNameProperty, name, NULL);

//create a multi field record
ABMutableMultiValueRef multi = ABMultiValueCreateMutable(kABMultiDictionaryPropertyType);

//create a dictionary to store parameters into
NSMutableDictionary *addressDictionary = [[NSMutableDictionary alloc] init];
[addressDictionary setObject:address forKey:(NSString *) kABPersonAddressStreetKey];
[addressDictionary setObject:city forKey:(NSString *)kABPersonAddressCityKey];
[addressDictionary setObject:state forKey:(NSString *)kABPersonAddressStateKey];

//add the dictionary to the multi field record
ABMultiValueAddValueAndLabel(multi, addressDictionary, kABHomeLabel, NULL);

//add the multi field record to our contact
ABRecordSetValue(person, kABPersonAddressProperty, multi, NULL);

//save our contact in the shared address book
BOOL didAdd = NO;
if (ABAddressBookAddRecord(ab, person, NULL)) {
	didAdd = ABAddressBookSave(ab, NULL);
}

Note that we are able to use certain objective-C classes (for example “NSMutableDictionary”) with the Core-level address book functions because of toll-free bridging. Very handy.

That’s all for now, I’ll post another sample sometime this week.
- Joe