Monday, January 26, 2009

ClickToFlash - Stop Automatic Flash Loading in Safari

Daring Fireball posted about ClickToFlash, a Safari or WebKit plug-in that stops the downloading of Flash in the browser and currently shows a gradient instead. Click to load Flash if you want. While this is fantastic, and I wish it were built-in to Safari, the gradient isn't enough to know that it could be clicked. For example, if you go to the site for the upcoming MMO Star Wars: The Old Republic, all you see is a blank gradient screen. There is an open issue for this on Google Code, so hopefully in an upcoming release it's changed.

Update
When I was cleaning up the download, realized it came with the source code. I then cut and pasted the code from the open issue and put it into the _drawBackground method in the code, compiled, and replaced the installed plug-in with my custom built version. Problem solved, I now see Click To Flash centered in the gradient. Excellent!

If you want my custom version, click here to download. Source and the plug-in included, no warranties of course

Updatex2
Jonathan 'Wolf' Rentzsch has taken over ownership of the ClickToFlash project, and released ClickToFlash 1.1 today. This version never implemented the Click To Flash text, which was the change I made, with an image pictured left. The other big feature is a whitelist, so you can always load Flash on selected sites. This installer for this version will replace my custom built version if you installed that.

Sunday, January 25, 2009

GeoNames Web Service Crushed Under Load From Free iPhone App

The GeoNames blog posted today What to do against DDOS effects? They aren't suffering from a malicious DDOS attack, but a free iPhone game called iMob Online is using the GeoNames web service for data. The app has become so popular that the GeoNames free web service has been crushed under the load.

In my iTimeZone 1.1 Post-Mortem, I talked about the choice to use the GeoNames CSV files instead of the web service for city data because, in part, the web service took control of the user experience away from me and put it in the hands of a potential fickle network resource. I feel bad for the GeoNames and iMob teams, no one could really predict this on either side. It again highlights though the inherent risk of building a product around free web services with no recourse if something goes horribly wrong.

Wednesday, January 21, 2009

US Democracy Server: Patch Day Version 44.0

If you have ever read an MMO's patch notes (like the recent released World of WarCraft Patch 3.0.8, then the US Democracy Server: Patch Day Version 44.0 release notes are hysterical, or at least me and Michael Jurewitz did. Thanks for tweeting the link Mike.

Promoting iTimeZone or I Didn't Know I Had To Be A Marketing Exec

I thought I was reasonably well prepared to be a moonlighting indie iPhone application developer. Here's what I thought was required before I got started:

Application Idea...
Intel Mac for Development...
Accepted into iPhone Developer Program...
An Icon I Liked...
Application Name...
Company Name...
Blog (hey, your reading it!)...
Twitter account...

In addition to all the development, UX, and graphics work I had to do, I planned on blogging and tweeting for marketing. That should be enough right? Wrong! I was shocked to learn I also had to become a marketing exec!!!

Rewind
When iTimeZone 1.0 was released, sales where higher than my optimistic projections. I had been seriously worried that I would not break even on my modest initial investment. iTimeZone was not bringing in get rich money, just better than expected. As summer turned to fall, sales kept gradually decreasing. I was already working on iTimeZone 1.1, feeling some pressure to get the new release out due to customer feedback. I was also really counting on the new release of iTimeZone to push sales back up, just as I had grown to expect from reading from various Mac indie developers over the years. Reading the PCalc 1.1 for iPhone post-mortem threw cold water on those expectations, since I learned that new versions of existing apps no longer pushed apps to the top of the Release Date filter in App Store category view. iTimeZone is in the Travel category. Apps only get listed in one category. I had low expectations then that releasing iTimeZone 1.1 would be sales invigorator until I read Benjamin Ragheb's comment, which said to change the availability date of the app in iTunes Connect. That did the trick!

Onward and Upward
When iTimeZone 1.1 was released, sales did increase a good amount, but the effect doesn't last. I am not sure I have seen even a modest boost with the release of iTimeZone 1.2. As the number of apps is only increases and accelerating (see image on left, 15,000 app, 500m downloads as of this post), you don't stay on top of your apps category by Release Date for long. Especially when a city guide, for example, is released as one app per city all on the same day, resulting in 30 separate apps. If you are lucky enough to get a prominent property with a large audience to mention your app, you will get a huge boost. Right after iTimeZone 1.1 was released, we unexpectedly got it.


The Apple Boost
It is hard to overstate what a sales difference being on the front page of the iTunes App Store home page is. Sales increased several orders of magnitude. Even better, iTimeZone wasn't leaving the front page for a whole week, and would then get shuffled back in the NEW box until it dropped off after a month (there are 4 pages). Did iTimeZone make the 100 Top Paid Apps? Yes, incredibly, iTimeZone climbed to #75 as far as I can tell.

Pretty soon after iTimeZone was on the App Store front page, Apple launched the iPhone Your Life site, which included a World Travel section. This was the first indication that Apple was going to break out top apps by paid and free in each category, and iTimeZone happened to be the top paid travel app when the World Travel site launched.

Being promoted by Apple is the best marketing you can get, it's free, but its impossible to quantify how to get it. That said, marketing your app is your job, not Apples. The deck might be slightly stacked against you on the App Store, but there are things you can do, or at least try.


Meanwhile...


Not the actual ad
Click through to the product page


Not the actual ad
Click through to the product page

I had looked into using Google AdWords for iTimeZone weeks before 1.1 shipped, even setup the ad (pictured left) but was holding off turning it on until 1.1 was released and the web site was updated. I activated the ad on a very modest budget of $50/month. I was floored the morning when 30% of my budget was burned through overnight, in about 13 hours. I was using up my budget at a pretty good pace, and I changed the ad (pictured right) to use action words (e.g. Buy) and just let it go for a while. I found AppCubby's blog post in early December (ok, someone in my feed list linked it, sorry, can't find that source) about marketing iPhone apps. The post articulates something that was nagging at the back of my mind since turning on AdWords, how do I correlate the clicks on the ads to actual sales? You can't, and the price of keywords is only going up! AdWords itself told me that my ads weren't running enough to be effective by late December (advertisers, recession, have you heard of it?), so in early January I stopped trying to run the ads until I can dedicate the time to figuring out how, or if, I can effectively use some form of advertising.


What's Next and Lessons Learned
What is a realistic expectation for iTimeZone sales? Should I set the goal to be sustainable Top 100 sales across the whole store? Seems highly unlikely given the number of apps in the store, the rate of new apps, and the kind of app iTimeZone is. iTimeZone is a use once in a while, or in short bursts (e.g. planning a trip) app, so it is never going to attract the same kind of attention that say, a farting app will. How about Top 100 Paid in Travel? That seems doable, but I am going to have to take some action, aside from what I am already doing, to sustain or hopefully increase ongoing sales. There are numerous next steps that I can take, and I will probably try most or all of these.

  • Press Releases. This seems like a no brainer now. Both directly to key sites, and trying a listing site like prMac.
  • Promo Codes. Since Apple has unveiled the ability to give away promotional copies, going to have to figure out a group of people I would like to get iTimeZone in front of and cold email them or something offered the promo codes. Of course, I could send out promo codes to anyone that has contacted us for support. Not huge reach, but a token of appreciation maybe.
  • Lite version. There has been some mixed advice, some like iShoot have seen phenomenal new success with existing paid apps after releasing a Lite version of their app. I have read it really hasn't moved the needle at all for other apps. Seems to me though a lower featured version of iTimeZone would be essentially good free publicity, and maybe some number of iTimeZone Lite downloaders will convert to iTimeZone buyers.
  • Interviews, Case Studies, and Publicity. These are much harder to pull off, but if you are asked to participate in any of these, you need to go for it. I have a few opportunities I am hopeful will pan out.
  • Podcasts. I draw the distinction here between interviews, where you don't run the podcast, and running your own podcasts. I can't see doing this right now.
  • Alternate versions. Since apps are only listed in the App Store once, perhaps segmenting the app, if it makes sense, into different tiered features and pricing. A Lite version if the typical one, but I am also considering what I am thinking of as a Pro version of iTimeZone, probably in another category.
  • Release another app. If you only developing one app, not only are all your sales tied to that app, so is your exposure. If Apple decides to consume the idea you are shipping in the OS, you are out of business. If people like your one app, you don't have another to sell them. If customers aren't interested in your app, but like how you are doing things, you don't have another app to potentially sell them. It is easier to sell to existing customers than new ones, essentially you are potentially leaving money on the table.
  • ABS. Always Be Selling. I think natural sales people just know when and how to sell, and how to market their wares to people. Even if you aren't a natural, if you are talking to people you know or at conferences and such, you have to look for openings to sell yourself and your work.

While as a developer you might not have thought it necessary, or you even find it distasteful, the simple truth is that marketing is The One Thing Every Software Engineer Should Know. Ignore it if you don't want anyone to buy, use, or enjoy your work.

Update
This post on Majic Software Jungle has some good info on pricing strategies, which I intentionally avoided in this post because concrete data on what works or not is pretty hard to come by.

Thursday, January 15, 2009

iTimeZone 1.1 Post-Mortem, or Limiting Features Based On How Little You Know

iTimeZone 1.2 is out, so I figured it was finally time to do a post-mortem on iTimeZone 1.1.

Picking Your Battles
One of the challenges when trying to ship a product using a new development framework is limiting the amount of stuff you need to know to implement your features. This is particularly acute for indie developers since they are either a team of one or few. Dividing up feature assignments according to unknown frameworks isn't a viable tactic. Instead, shipping features have to be limited to minimize the frameworks the developer(s) must learn. That reasoning led to iTimeZone 1.0's city list being extracted from the built-in iPhone OS time zone data. There was too much engineering to make a large city list work for someone (aka me) that had never done any real OS X, Objective-C or Cocoa development and make a July iTunes App Store release in their spare time. It was obvious that a large city list had to be in iTimeZone 1.1, the question was just how to implement it.

Missed Wide Right
There where two possible paths, and the frameworks those approaches mandated, to get cities:

  • Web Service:
    • Threading. Every character a user types starts a new search. In this case, a new web service call
    • XML Parsing. Going to have to crack the results and create objects
    • Caching. Should cut down on web service queries by storing user selected cities and search results locally, either in memory or on disk
  • Local SQLite Database
    • Threading. Every character a user types starts a new search. In this case, a new DB query
    • SQLite. Needed to store databases locally and execute queries
    • Caching. If queries are expensive, cache results in memory

Deciding which engineering approach to use largely depended on the data source for the large city list, and how did they provide the data. I found GeoNames, which not only had a web service but an export of their city list in CSV format. That didn't help, GeoNames was too flexible, it was completely in my court.

I decided to go for using a Web Service first for these reasons:

  1. The app and others are going to need all kinds of data from the Web in the future, this is the most reusable investment
  2. Everyone uses web services, it has to be fast enough
  3. Every iPhone has permanent connectivity, and iPod touch has WiFi, requiring a network connection won't be a big deal
  4. I didn't want to worry about stale data.

It turns out I was wrong on all four points above! Here's why in order:

  1. I was only partially wrong on this. Yes, iTimeZone or other apps are going to have to pull data from the Web, and I figured out how to do this in Cocoa, but I wasn't going to be using it because...
  2. Geonames.org's Web Service's (at least the free one) performance was too unpredictable. I could be in a full strength 3G signal, and wouldn't receive first results from the service for many seconds after tapping a character. It worked eventually usually, but I never felt comfortable with it because I couldn't guarantee what end users would experience. That is kinda endemic to Web connected apps to some degree, and it will be acceptable for a lot of data, I wasn't happy with it for iTimeZone
  3. iTimeZone has to work without a network connection. I don't know why I ever thought it could require a network connection. We here at Tangerine Element use it in places that are likely to have limited or no connectivity, and getting on the WiFi outside of known hotspots turns out to be an exercise in frustration.
  4. City and time zone data is relatively slow changing. Sure, city names change and governments change daylight savings rules all the time. I realized though that I could simply continue using the iPhone's built-in time zone data, the offsets and effective dates, matched up based on the time zone id.
I sunk over a month building a prototype to pull city data from Geoname.org, learning threading to make it more responsive, and figuring out how I was going to cache the data, but at some point performance wasn't predictable enough and network connectivity so spotty were I was testing, I bit the bullet and gave up on it.

So You Want To Use a Database on the iPhone...
Most of what I had learned about threading and caching large results sets because of the GeoNames prototype was applicable when using SQLite. Using SQLite though had it's own set of challenges.

  • SQLite is a C, not Cocoa, library. This meant understanding how to convert between Objective-C data types and what SQLite expected. Mostly easy, with the exception of how to format LIKE queries.
  • There are a bunch of calls and interaction patterns to get right. I mostly used the SQLiteBooks sample application Apple provides to iPhone SDK members to understand this, but there were a few things I learned from the APRESS book The Definitive Guide to SQLite. I confess though I haven't read it cover to cover, just spot read where I needed to learn something.
  • Mac OS X only comes with command-line tools to create SQLite database. So I bought SQLabs SQLiteManager for $49. Easily worth it for the ability to easily import CSV files into database tables.
This all had to be done largely because CoreData doesn't exist on the iPhone OS 2.2 and below. Eventually though by importing the GeoNames cities CSV file into a SQLite database, and some massaging of the data, I had a pretty good city list.

Lessons Learned About iPhone Development
What did I learn going while developing iTimeZone 1.1?

  • The SQLiteBooks example is not the ideal pattern. This is perhaps pedantic, but I should have just created a SQLiteController and put everything in there. I haven't done this yet even in iTimeZone 1.2, but when I feel like it, I'll probably move stuff around so that if CoreData drops in a future OS release, my storage code is already disentangled from AppDelegate.
  • Preparing SQLite statements is time consuming. I ended up putting the preparation of the most commonly used statements in another thread at launch, the delay doing them inline was pretty high the first time, and there was very little reason to make the user what when a thread could do the work in the background.
  • Consider using a third-party library. The one I know of is Gus Mueller @ Flying Meat's FMDB. I choose not to use it because I was nervous about committing to it only to find it didn't do something I wanted and having to add it myself, but this is another decision I might reevaluate depending on the CoreData situation.
  • Creating threads might be expensive, reuse them. I still haven't done this in iTimeZone 1.2, I am creating and destroying threads for every character typed into search, but that is probably not a high enough usage to justify the rewrite at this point since it performs well enough.
  • Use the Leaks Performance Tool. No matter what, you are going to leak memory, make sure you are using this tool to find and stop memory leaks.
  • Do as much on background threads as you can. Pretty much since device OSes have had threading this rule stands, but just to reaffirm it's truth. My examples are preparing SQLite statements and loading the top X number of cities after the UI appears.

Monday, January 12, 2009

iTimeZone 1.2 Released to the App Store

On Monday January 12, 2009, iTimeZone 1.2 was released to the iTunes App Store. Here is the list of what's new or changed. We are already investigating features and UX enhancements for iTimeZone 1.3 (or is it 2.0?), but as has become Tangerine Element's custom, features and changes won't be announced until they are done and being tested or in App Store review. Some changes are still being trickled out (screenshots in App Store, Tangerine Element site revision), but should all be done by tomorrow.

What Can Be Learned From Rejection?
Due to the change in UITableView's expectations for numberofRowsInSection and the bug because of it not being caught in testing, the first time iTimeZone 1.2 was submitted for approval, it was rejected! This was the first time a submission of iTimeZone was rejected. It was interesting because it showed that people are manually testing and approving applications. I had thought it was a 50/50 shot the apps were being run through a battery of automated tests and maybe manually running it on the device, then you got approved if it all passed. It was clear though from the iTimeZone 1.2 rejection evaluators are actually using the app, and providing crash logs if needed. How nice!

Thursday, January 08, 2009

iTimeZone 1.2 Feature Complete

iTimeZone 1.1 was released on Tuesday October 21, 2008. Work was started quickly on iTimeZone 1.2 with the intention of turning it around very quickly. The only planned feature for 1.2 was Change Top City. A couple of software updates to 1.1 (1.1.1 and 1.1.2) slowed down 1.2 a little, but the plan really came apart due to completely unforeseen events. Here's what's changed:

Change Top City
When iTimeZone 1.0 was designed, the intention was never for the top city (the one that the time wheels show the date and time for) to change in the app. If the user wanted to change the top city, they would change the Date & Time city in the iPhone/iPod touch Settings app and iTimeZone would match it. This will be talked about more in an upcoming iTimeZone 1.1 post-mortem, but during 1.1 development it became obvious through internal usage and user feedback (thanks all) that changing the top city had to be in the app.

In iTimeZone 1.2 users can double-tap any city in the list to make it the top city, but there's a catch. The time wheels (which is a UIDatePicker control) don't always calculate correctly the date and time after changing the timeZone property, which iTimeZone has to do when changing the top city. This is one of the unforeseen events I mentioned,and a bug has been filed with Apple (#6452008). Hopefully Apple fixes this issue in a near-term iPhone OS release, or I might have to investigate alternative ways to implement the time wheels. In the meantime, I have chosen to handle this condition by alerting the user once per iTimeZone session when the top city changes about the issue as shown in this screenshot:

 

UI Tweaks

 1.11.2Changes in 1.2
Main with Cities
  • Country Flags. A popular request, makes it easier to quickly ID a city.
  • Bold Fonts in the City List. Makes it all easier to read and pop.
  • Weekday on City Date. Whenever a date is shown in the city list, it now had the short day of the week
  • Shrunk the city cell height. Made it just a tad smaller so the top line of the fourth row is visible.
  • Faster Scrolling. Doing all drawing manually instead of letting controls lay themselves out on screen.
Add City
  • Back Navigation. Removed the city's time zone so that the screen title was better positioned
  • Automatically put focus in the search box and loaded the keyboard
Add City - Search Results
  • See Full State/Region and Country. Each cell has two lines, state/region and country can be seen without abbreviation, while emphasizing city name
  • Country Flag. Top aligned with the city name
  • Faster Scrolling. All drawing done manually instead of laying controls out by themselves
Main Edit
  • Custom Drawing in Edit. Removing the day/night icon to focus user's attention on the delete and move icons.
City Detail
  • State/Region Added. For completeness
Settings are in the Settings App
  • Always Show Dates. If you don't like seeing Today, Tomorrow, Yesterday when the top city's date is today's date, then this is for you. The default as with past releases is OFF.

What's Next
iTimeZone 1.2 has been testing and is in App Store review, which hopefully won't take too long.

Monday, January 05, 2009

UITableView's numberOfRowsInSection behavior change from 2.0 to 2.2

Decided with iTimeZone 1.2 to upgrade and build against iPhone SDK 2.2. It seems like UIDatePicker is just *slightly* more reliable when configuring its timeZone property after it had been initialized. However, this resulted in an unintended side effect of The app crashing when removing all rows in the main city list table. It took me a couple hours to understand what was going on, but if you see the following error in console:

*** Assertion failure in -[CityTableView _endCellAnimations], /SourceCache/UIKit/UIKit-747.36.52/UITableView.m:679
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update must be equal to the number of rows contained in that section before the update, plus or minus the number of rows added or removed from that section.'

The cause, at least for me, is a mismatch in the expected return value of tableView:numberOfRowsInSection: that your code (most likely a UIViewController or UITableViewController instance) implements as part of the UITableViewDataSource protocol.

If your app is built against iPhone SDK 2.0 or 2.1, the correct behavior is to return 1 in tableView:numberOfRowsInSection: when you have no actual content and provide an empty cell in tableView:cellForRowAtIndexPath:. This does not cause tableView:deleteRowsAtIndexPaths to throw an exception when removing the last row.

If your app is built against iPhone SDK 2.2, the correct behavior is to return 0 in tableView:numberOfRowsInSection: when you have no actual content. tableView:cellForRowAtIndexPath: is never called. When your code calls tableView:deleteRowsAtIndexPaths as part of a row delete operation, everything works fine instead of crashing. An alternative correct behavior, if a UITableCell does something special (e.g. add a row, load more rows) is to always add 1 to the actual count of the items in the, most likely, NSMutableArray and return that in tableView:numberOfRowsInSection:, taken correct conditional action in tableView:cellForRowAtIndexPath: for putting your special cell at the top or bottom of the UITableView.

Hopefully you have come upon this article or figured this out on your own before submitting the latest release of your app to the App Store and got rejected for having a crasher in a main use case, just like, uh, someone I know did because no testers found the issue and you didn't rerun all use cases yourself after building against a new SDK version ;-)