TimerDoro: a timer app that’s less ghetto than GhettoDoro

As an update to this post, I wasn’t able to sneak the GhettoDoro app by the App Store review process. I intentionally left the UI just about as rough and barebones as possible because the idea of getting Apple to publish an intentionally bad-looking app of mine would be funny. I think this is mostly The Smart-Ass 8th Grade Version of Me that thinks things like having an app named GhettoDoro is glorious.

After getting totally rejected by Apple, I decided to take GhettoDoro and use it as a platform to learn about unit testing and TDD in the Cocoa and Objective-C world, namely the OCMock and XCTest frameworks. It was pleasant using TDD to drive the process of design and refactoring — the timer logic is now in a model class so that the view controller can stay unpolluted.

I would have been perfectly content to leave this project unreleased, but after a week, I found myself using GhettoDoro more and more often. It’s actually useful! That meant it was worth polishing up and having its interface be more than just a juvenile punchline.

One thing that stuck out at me while reviewing the iOS 7 UI design recommendations from Apple is that they champion a full-screen design modality where the entire screen is used to express information. Their Weather app is a great example of this. I wanted to take a swing at an interface like that as well.

My thinking was that the user should be able to look at the screen and tell how much time they had left without actually looking at the time left. What I came up with was to have the background turn from green to red as it ticks down. Green and red are universally known as stop/go — hilarious to a colorblind person like myself — and it also fits in with the theme of tomatoes in the Pomodoro Technique, like how a green tomato is fresh and a red tomato is ripe.

One complication to implementing this feature was that the color needed to be based on the proportion of the time left vs. how much time was added to the timer. For example, if you start a 5-minute timer, it should be just as green as starting a 1-hour timer. And if a timer is almost red, adding 5 minutes to it should not push it back to full green. That meant that the color had to be calculated with a ratio multiplier, where the ratio is based on the current time left and the total time added to this timer.

There’s also a lot of behind-the-curtains trickery involved to support background notifications and pausing/resuming the timer. It’s was a bit challenging in the sense that there are a lot more potential cases to have to consider. The timer can be started/stopped/paused/resumed, but the app can be in the foreground or the background as well. And when it’s paused and and started again, does the notification get rescheduled to the new timer end date? That sort of stuff.

These different use cases and scenarios can be tough, but taking a TDD approach helped immensely. It allowed me to really focus on the app requirements, while writing the production code was almost a secondary concern. The beauty of TDD is that it lets you really build your system to do what you want it to do.

As I am finding out in a current project however, there’s one exception where it makes sense to drop TDD, and that’s during a spike when you need to explore a problem that you don’t have enough experience to know how to design, approach, or solve. You can’t write a test to specify what your code should do if you just don’t know what it should do. Even if you think you know what it should roughly do from a black-box, outside-in, functionality perspective, your initial assumptions about your code’s API and its expected behavior is likely to change.

I feel like a good way to tell if you should drop TDD temporarily is if you’re writing a unit test and you say to yourself, “I am pretty sure I will need to drastically rewrite this test.” In that case, there’s no point writing something you’re likely to junk entirely. Of course, the rub is that you have to circle back and make sure you write that unit test afterwards.

GhettoDoro, The World’s Worst Pomodoro Timer

I am a believer in the Pomodoro Technique, a very smart productivity hack to structure your work efforts in 25-minute sprints, punctuated by 5-minute breaks.

Lately, I’ve been using a mechanical Ikea timer, and I hate it. It makes an awful ticking sound, and its alarm (when it actually manages to work) is jarring. I considered using the iOS 7 built-in timer, but switching between 5- and 25-minute increments is clumsy. All I need for a timer is to start/stop/pause/resume it, and to add in 5-minute increments. Is that so hard?

I know there are probably some very good Pomodoro timer apps out there, but I thought building one myself would be a good way to learn more about how to use timers in iOS.

So I built one myself. And I made it revel in all of its brain-dead-simple glory. It’s so simple that I christened it GhettoDoro. I even ripped the Windows 3.1 startup sound to use for the alert. I made the app icon the ugliest tomato (pomodoro) you’ve ever seen.

And I submitted it to the App Store, and I totally expect it to be rejected! There are tons of timer apps out there, and mine is definitely way uglier, but damnit, I’m going to use this thing, and I’m going to enjoy it.

And in case you were wondering: yes, I learned a lot of good things from this short project.

I know how to create an NSTimer. I know how to schedule it on the run loop to repeat. I know how to invalidate it to stop it from firing again.

I know how to set up an UILocalNotification, which is the class you use when you need to have your app display a notification to the user (banner or alert style). I know how to register it with UIApplication to run at a specific time (fireDate), and how to cancel a scheduled notification (cancelLocalNotification).

I learned a lot about working with time intervals.

And I made my first dalliance into the AVFramework by using the C API to play the alert sound and vibrate.

And I wrestled mightily with the Apple Developer certificate signing and device provisioning, and I have a much better sense of how it works.

So all in all, my ghetto Pomodoro timer app was a rousing success!

Circling back on iOS

Having completed the fairly comprehensive iOS online course offered by Stanford on iTunes U, and having completed ColorMyWorld as my “final project”, it’s time to circle back on some important topics that I haven’t covered yet or only skimmed the first time around:

  • Unit testing
  • Memory management that’s not ARC (i.e. pre-iOS 5)
  • Reading the Apple Human Interface Guidelines
  • Fully understanding the app submission process: certificates, provisioning profiles, etc.

There’s so much more I want to learn, though:

  • Core Graphics (Quartz)
  • Core Animation

But then even apart from iOS, there’s a desire to explore other topics: web, deeper CS theory and mathematics, etc. Too many paths, not enough time and energy to explore them all.

ColorMyWorld app submission

I managed to submit and upload ColorMyWorld to the App Store on Friday. It took longer than expected to put the finishing touches on it.

iAd Integration

The last-minute iAd integration was trickier than it should have been because the best practice for iAd integration changed in iOS 6, and most of the googleable resources on it are iOS 5 and below. Thankfully, the iAdSuite sample project that Apple created shows the most important parts:

  • Use a single central delegate object to manage the ADBannerView. The sample code uses a BannerViewController class that’s instantiated in application:DidFinishLaunchingWithOptions: and sets that as the root controller.
  • The BannerViewController class is a container VC. It wraps a content view controller — in ColorMyWorld’s case, a navigation controller on iPhone and a split view controller on iPad — by adding it as a child VC, along with the ADBannerView. This is done in its loadView method.
  • In BannerViewController’s viewDidLayoutSubviews method, it creates the appropriate frame for ADBannerView by calling
    [bannerView sizeThatFits:self.view.bounds.size];
  • Once the ad banner view is sized properly, we can adjust the content view’s frame so that both fit together comfortably

The problems I ran into were:

  • At first I tried using the ad banner view that you drag out from XCode. Getting it to resize on device orientation changes was a bit of a hot mess. In retrospect, I wonder if I could’ve just called sizeThatFits: on it, but I think to do so it needs the iOS 6 method of initialization (initWithAdType:ADAdTypeBanner), which I’m not certain that it does if you drag it out from XCode.
  • I was trying to not modify the app delegate to get this working. I tried to create the BannerViewController in the storyboard and set it as the root VC, but then I wasn’t certain how to get it to load my custom content VC. In retrospect, I could probably do this by modifying BannerViewController to expose its content VC as a public outlet, then create a subclass that would load my custom content VC on initialization (would probably have to be in an init method rather than viewDidLoad).

One suggestion from Apple that I did not implement is to remove the ad banner view from the view hierarchy when the ad banner won’t be on screen for a while. The idea is that you don’t keep it around when the ad isn’t viewable so that ads aren’t wasted. In ColorMyWorld, this occurs when the UIImagePickerController is presented modally on the iPhone.

The reason why I chose not to implement this is because I’m still considering whether users will spend enough time taking, selecting, and editing pictures to justify doing this. If I was to implement this, I would need to extend BannerViewController to be able to remove and re-add its banner view via some public method call. The child content controller would call this by accessing its parent controller.

iPad

And for some silly reason, I decided last-minute to implement iPad support. I had to dig up some of the slightly-decaying knowledge of split-view controllers. Setting the master and detail VCs, the weird detail about having to implement the delegate method that says, “DO NOT HIDE MY MASTER VC.” I had to test some unforeseen user workflows, since many of the screens that are presented full-screen modally on the iPhone are not on the iPad. Oh, and I had to think about implementing a popover for the first time.

Most challenging, however, was some weird behavior I noticed in one particular use case where a user taps the Photo Library button before tapping the Camera button. The camera would end up displaying just short of full-screen, with a gap where the status bar would be, and the camera controls at the bottom shifted and clipped by the same gap. Weirdly enough, closing and opening the camera again would get rid of this behavior.

I tried everything. At first I thought it was some weird interaction between the UIImagePickerController and the UIPopoverController — maybe displaying the photo library in the popover caused it to get confused when I then asked it to display the camera mode, and it didn’t update its frame properly? Maybe I had to dismiss the image picker when the user dismissed the popover. Maybe I had to set the modal presentation style to full screen before displaying the camera?

Nope. Nothing.

In the end, the only solution was to set the UIImagePickerController to nil when the user dismisses the popover, which then causes a fresh instance to be created when the user picks the camera again.

Very, very weird behavior.

App Store Assets

Another pain in the neck was creating all of the art assets required for App Store submission. Icons of every different size, and screenshots to use as launch images. I had to fire up GIMP for the first time in a while. It was a bit tedious creating screenshots for each device.

App Store Hoops

And finally, the actual submission process on iTunes Connect is extremely jargon-y, complex, and confusing for a noob like myself. You create an app record, which requires that you enter your app’s Bundle ID, and there’s a SKU you create, and that creates an App ID, and you have to provision a distribution profile, and once you associate the App ID with the distribution profile, then you create an archive, and then you test the archive, and oh my god what is going on.

I managed to limp through it. I’m not 100% certain I did it right, nor do I fully understand what’s going on, but I most certainly will circle back to it next time and try to figure out what’s going on. At least this time I’ll know a little more what I’m getting myself into.