Starter Step

Brian and I have been working on improving the startup of TripCase and have found some interesting items. Our startup on TripCase slow…eventually we think we will move to a more facebook style model where much of your data is stored locally on the device. This will allow us to show the main view of TripCase without the 6-N seconds of time to talk to the server first. While the page loads with the cached data we can asyncronously retrieve the live data and update the view….but I digress.

We were debugging our message stream on the main view and noticed that the first cell was taking almost a full second to create. All subsequent cells were taking on the order of .15 – .20 seconds. After some NSLogs here and there we finally tracked down the culprit! In our app we implemented a “time ago” feature that tells you relatively how long ago something occurred. It is a nice feature and lets users see that something was created a few minutes ago, a few hours ago, etc. Its pretty granular and makes for a good experience. The times we convert to timeago are given to the client through our server and are all in GMT. This means that in timeago we need to create a NSDateFormatter with a mask that includes timezone. Our poor performing code looked something like the following:

+(NSString *)timeAgo:(NSString *)_date {
	static NSDateFormatter *parser = nil;
	if (parser == nil) {
		parser = [[NSDateFormatter alloc] init];
                [parser setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];      
		[parser setFormatterBehavior:NSDateFormatterBehavior10_4];
		[parser setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss z"];
	}
	NSDate *date = [parser dateFromString:[_date stringByAppendingString:@" GMT"]];
	double littleOverAWeek = 11520.0f;
	NSDate *now = [NSDate date];
	double deltaMinutes = fabs([date timeIntervalSinceDate:now]) / 60.0f;
	if(deltaMinutes < littleOverAWeek){
		return [NSString stringWithFormat:@"%@ ago", [self distanceOfTimeInWords:deltaMinutes]];
	} else {
		return [NSString stringWithFormat:@"on %@", [DateFormatter shortDateFromDate:date]];
	}
}

In the above method, we create a NSDateFormatter with a mask of “yyyy-MM-dd’T’HH:mm:ss z”. Seems pretty harmless right? Wrong! Brian and I removed the “z” and timed the creation of the NSDateFormatter. We saw that with the “z” in the mask, the creation of the NSDateFormatter was .7 seconds slower!! 700 milleseconds is a lifetime. We saw that during startup we create two NSDateFormatters that were doing this…that is 1.5 seconds of wasted time that we wanted to remove.

In the docs we noticed that you can set the timezone for the NSDateFormatter explicitly. We did that and the .7 seconds of invocation time went away…BAM! We just shaved 1.5 seconds from our startup time. Our new code looks something like this:

+(NSString *)timeAgo:(NSString *)_date {
	static NSDateFormatter *parser = nil;
	if (parser == nil) {
		parser = [[NSDateFormatter alloc] init];
                [parser setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];       
		[parser setFormatterBehavior:NSDateFormatterBehavior10_4];
		[parser setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss"];
		[parser setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]];
	}
	NSDate *date = [parser dateFromString:_date];
	double littleOverAWeek = 11520.0f;
	NSDate *now = [NSDate date];
	double deltaMinutes = fabs([date timeIntervalSinceDate:now]) / 60.0f;
	if(deltaMinutes < littleOverAWeek){
		return [NSString stringWithFormat:@"%@ ago", [self distanceOfTimeInWords:deltaMinutes]];
	} else {
		return [NSString stringWithFormat:@"on %@", [DateFormatter shortDateFromDate:date]];
	}
}

Two things to notice in the above code:
1. We set the timezone context directly on the NSDateFormatter which allowed us to remove the “z” from the mask.
2. Doing this also allowed us to remove the ugly code that was appending GMT to the time returned from the server.

All of our timings were done on a 3G device. In all we shaved roughly 1.5 seconds because this pattern was used in two places during startup. Score 1 for super sleuthing!

Last night Apple’s latest commercial premiered with our mobile travel app, TripCase, as the first application demoed in the advertisement.

It ran during Heroes, House, and Gossip Girl, and Apple plans on running it for the next several months in various time slots.

If you haven’t seen it yet you can view it on Apple’s website here:

http://www.apple.com/iphone/gallery/ads/#dine-large

I did a write up a while back on a custom UIAlertView that displays a list of options to the user. I’ve gotten some questions on how to use the component so this is a quick write up on just how to do that. Let me first say that I have not done iPhone development for a few months….I have been hard at work getting TripCase (http://tripcase.com) working on Windows Mobile. I hopped back into XCode this evening and felt lost!

So to recap the component, the intent is that you have a list of options you want to show your user. In our case we are geocoding a location that can be resolved to multiple places. We want our user to choose from a list of places returned from our geocoding service. We could have taken the user to another page and had them make a selection and then taken them back to the original page. We weren’t crazy about that…so we rolled this little gem.

Also, just as an fyi, this may not even be needed anymore. I have not looked at how the 3.x sdk would handle this. There might be a nicer approach to this all together. But for those who have asked, here is a sample ApplicationDelegate class body that should get you going. I’ll also attach a zipped up version of the sample app for you to download and use.

- (void)applicationDidFinishLaunching:(UIApplication *)application {    

    // here we just create a button to invoke our alert view.  this of course would be done by some event in your code somewhere.
	UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
	button.frame = CGRectMake(60, 100, 200, 30);
	[button setTitle:@"Invoke the Alert View" forState:UIControlStateNormal];
	[button addTarget:self action:@selector(createAlertView) forControlEvents:UIControlEventTouchUpInside];
	[window addSubview:button];
    [window makeKeyAndVisible];
}

-(void)createAlertView {
	[[[[AlertTableView alloc] initWithCaller:self data:[NSArray arrayWithObjects:@"one", @"two", @"three", @"four", @"five", @"six", nil] title:@"Did you mean..." andContext:nil] autorelease] show];
}


-(void)didSelectRowAtIndex:(NSInteger)row withContext:(id)context {
	[[[UIAlertView alloc] initWithTitle:@"Selection Made" message:[NSString stringWithFormat:@"Index %d clicked", row] delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil] show];
}

//-(UIView*) viewForOptionAtRow:(NSUInteger)row {
//}

This sample app just creates a button and throws it on the screen. The click handler for the button is where the use of the new AlertTableView comes into play. The AlertTableView is created with an array of option strings. The caller is “self” because I want this same class to handle the callbacks for the component. I am passing in a nil context because I dont need a context for this small example. I then show the AlertTableView as you would a regular UIAlertView.

Once we have created our new alert, there are two important delegate methods we have at our disposal. The first is didSelectRowAtIndex which will be called when the user clicks either an option in our list, or the cancel button. This method will receive the index of the selected row or -1 if cancel is clicked. It will also get the context that was passed into the creation of the alert. This is where you actually “do” something based on your users selection. The other method you optionally implement is viewForOptionAtRow. This method gives you the ability to customize the view that is displayed in the option list.

That’s pretty much it…. Of course the component needs work..it uses absolute postioning…and makes some bad assumptions…but it gets the job done.

Link to project
Ok so wordpress wont let me upload a zip file…? So I just googled for a free file hosting service and dumped it here Example App.

If you have ever chatted with me about programming languages, its terribly obvious that I love ruby. Its just plain fun to write ruby code. I wouldn’t call myself a ruby nazi, but I would say that I get excited when I can do ruby like things in other languages. I have been working with C# for about two months getting TripCase on the Windows Mobile platform. My appreciation for c# is actually growing more and more. We use lambdas quite frequently in our Windows Mobile implementation which makes our code look alot like ruby.

Just yesterday I wanted to titleize a piece of text. My goal would be to just call:

   "jake loves ruby".Titleize();

Unfortunately the method wasn’t available….until I wrote this:

public static class StringExtensions
    { 
        public static string Titleize(this string text)
        {
          CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text)
        }
    }

Now I can call my Titleize method as if it were a method on an instance of the String class. Cool huh….yeah I thought so. You can read more about extension methods (thats what the c# kids are calling it) here

Update:
While this is cool, I could not figure out how to take an already existing method on a class and extend its behavior. In ruby we can do this with our method alias trick….but still..props to c# for extensions.

I have just added simple scripting support to UISpec. It allows you to send a String as a script and have it run dynamically. This really opens up the possibility of using UISpec with other languages like Ruby, Java, etc. The following is taken from the documentation found here.

UIScript’s goal is to provide a simple lightweight scripting language that supports all the functionality of UISpec, but without having to write Objective-C code. Instead you write UIScript which is very similar to Smalltalk.

Currently UIScript only supports straight messages, which means there is no support for things like variables and loops or decisions (for, while, if else, etc…). And right now UISpec recognizes only Strings, Integers, and BOOLs.

You can run a script in your test by calling the C function:

UIQuery * $(NSString *script, …);

Messages

Like in Objective-C, UIScript is based on sending messages to objects. The only real difference in writing UIScript vs Objective-C is that you take out all the dot notation and brackets.

$(@”navigationButton touch”);
$(@”tableView tableViewCell all should not be selected”);

As you can see it’s very similar to what you are used to writing, except without all the syntactic sugar, making the script even more readable.

Strings

Strings in UISpec are just quoted values like ‘Hello’ or ‘Good Bye’, and there is no need to use the @ symbol as a prefix like you do in Objective-C.

$(@”navigationButton label text:’Save’ touch”);

$(@”textField placeholder:’Username’ setText:’bkuser'”);
//If you need to include a String dynamically just use NSString formatting
$(@”textField placeholder:’Username’ setText:’%@'”, [Config userName]);

Integers

Currently Integers are the only number support in UIScript. We hope to have support for decimals very soon.

$(@”tableView should have numberOfSections:3″);

$(@”label text:’Returns’ parent tableViewCell should have accessoryType:3″);
//Or you can use NSString formatting
$(@”label text:’Returns’ parent tableViewCell should have accessoryType:%d”, UITableViewCellAccessoryCheckmark);

BOOLs

The BOOLs YES and NO are also supported in UIScript.

$(@”tableView tableViewCell first should have isSelected:YES”);
$(@”tableView tableViewCell last should have isSelected:NO”);

//Or you can use NSString formatting
int rows = $(@”tableView numberOfRowsInSection:0″);
$(@”tableView should have:%@”, rows==3);

Example

Here is the itShouldUpdateUserRoles example from the demo rewritten using UIScript

-(void)itShouldUpdateUserRoles {
[self addTestUser];

$(@”label with text:’Brian Knorr’ touch”);
$(@”label text:’User Roles’ touch”);

$(@”tableView scrollToBottom”);
$(@”label text:’Returns’ touch”);
$(@”label text:’Returns’ parent tableViewCell should be selected”);
$(@”label text:’Returns’ parent tableViewCell should have accessoryType:%d”, UITableViewCellAccessoryCheckmark);

$(@”label text:’Returns’ touch”);
$(@”label text:’Returns’ parent tableViewCell should have accessoryType:%d”, UITableViewCellAccessoryNone);

$(@”view:’UINavigationItemButtonView’ touch”);
$(@”view:’UINavigationItemButtonView’ touch”);

[self deleteTestUser];
}

Over the weekend I did some work on UISpec…mostly getting the “redo” feature working.  Redo let’s you rerun a chained set of queries that will return a result based on what is on the screen right now.  Internally the redo feature is being used for traversals and filtering, and I thought it might come in handy in your test scripts.  Here is a simple example:

//First let's grab the last UILabel under the first
//UITableViewCell on the screen
UIQuery *label = app.tableViewCell.label.last;

//Next we will check that the label's text
//is equal to "Start"
[label.should.have text:"Start"];

Now let’s say we execute some code that reloads the screen or goes to a new one, and afterward we want to check the text of a label in the same place as before but on the new screen.

We could find the label again with label = app.tableViewCell.label.last; OR we can do the following instead:

[label.redo.should.have text:"Finish"];

Calling label.redo will actually rerun the code that created it in the first place: “app.tableViewCell.label.last” except now it’s against the new screen. Acts kinda like a template to find the label. It’s not really a template though since you have to run it at least once before a redo can be called. Now it wouldn’t be too difficult to add finder templates to UISpec. Hmmm…maybe I will work on that next weekend:)

Over the weekend I made some small changes to the way traversals work in UISpec. As a result the following method names have changed:

  • descendants is now descendant
  • parents is now parent
  • children is now child

Calling these methods return exactly what you would except them to. The method parent finds the parent view, child gets you the first child view, and descendant retrieves the first descendant view. Of course each result is wrapped in a UIQuery object. And if you want all the views and not just the first, call the method all afterward. Here are some examples

//Finds the first child of the
//first UITableViewCell
app.tableViewCell.child;

//Finds the all child views under
//the first UITableViewCell
app.tableViewCell.child.all;

//Touches all the child views under the first UITableView
app.tableView.child.all.touch

//Both of these will find the first child UILabel
//under UITableViewCell
app.tableViewCell.child.label;
[app.tableViewCell.child view:@"UILabel"];

//Both of these will find all the child UILabels
//under UITableViewCell
app.tableViewCell.child.label.all;
[app.tableViewCell.child view:@"UILabel"].all;

//And of course the default way still works the same

//Finds the first descendant UILabel 
//under UITableViewCell
app.tableViewCell.label; 

//Finds all the descendant UILabels
//under UITableViewCell
app.tableViewCell.label.all; 

Also the documentation has been updated to reflect these changes.


  • Dave: I can tell you're a ruby guy because you forgot the 'return' keyword. Thanks for the tip though!
  • Chandrashekhar H M: Hi, Thanks its working fine in iOS 6 but not in iOS 7.0. Any Suggestion on this.
  • Coeur: To change a rootViewController, without all this TVNavigationController : myNewRoot = [[UIViewController alloc] init]; myNavigationController.view