For the longest time it seemed that releasing an update to an iOS app was a random whack-a-mole process that I’d invariably get wrong in some way. It was maddening, especially since iTunes Connect has only recently become a decent web application. By switching to Jenkins for continuous integration of my iOS app builds I’ve greatly improved my process, but things didn’t really improve until I created a checklist for keeping track of my releases.
Since I’ve been asked many times about this very topic recently – both at work and on Twitter – I thought I’d write a post about how I bring some sanity to my release process so my app updates are timely and predictable.
Plan for your release early
Perhaps the best thing you can do to get some consistency in your releases is to plan ahead; if the end-goal of your project is to release an app (which, lets face it, really should be the case) then you should treat your release artifacts as if they were as important as your code. The sorts of things that make up your release are:
- App Store description text
- App Store keywords
- App Store update text
- Screenshots for both iPad and iPhone/iPod Touch
- 512px app icon
- App binary IPA
Most of those points above don’t involve any code, yet constitutes a great deal of effort on your part. Getting your screenshots refined so you show the most compelling parts of your app, refining your description to showcase your app the best way possible, and tweaking the “updates” text so you can quickly convince your user that they should update to the latest version. Those are all important steps to refine, and if you don’t have an easy way of editing and versioning that information then you’re already running behind. These aren’t steps you should rush at the end of a release period, so take your time and do it right.
The root of my Github projects always contain the following two subdirectories: “src” which contains my Xcode project, and “AppStore” which contains all the assets required to roll out a release. As you can see from the attached screenshot, I include separate files for my app description, keywords, etc. This is also a great place to store any promo codes you’ve downloaded from the App Store, screenshots of your app, etc.
As you can see from the screenshot, you can tell that I’ve translated Docset Viewer into French, Spanish and Chinese. I use the great iCanLocalize.com service for translating the strings embedded in my app. When I’m getting close to a release I generate my new localizable strings files, send them off to iCanLocalize, and wait for them to be translated. At this time I also send off my Description.txt, Updates.txt and Keywords.txt (if it has changed). This makes sure that people in those locales can properly read my app store description page, and makes it more likely for them to buy and install my app.
An important step that many people forget is to localize your app screenshots as well. As you can see in the screenshot, I have separate subfolders in my “Screenshots” folder for each language I support. Not only do you have to take screenshots of your app in English, but you should take those very same screenshots in each supported language.
I typically use the iOS Simulator for my screenshots and use the “Copy Screen (^⌘C)” shortcut which takes a screenshot and places it in your clipboard. I then paste each screenshot individually into its own layer on a Photoshop document, from which I can save all layers as separate images, making the process of exporting my screenshots a bit simpler.
Create a test plan
It may seem like overkill for some small apps, but regardless of the size of your app, you should create a test plan. This can be as simple as a bulleted text document that lists out the various scenarios you should test. For example, mine contains entries like the following:
- Suspend the app in the middle of a download
- Start a download when another is already in progress
- Delete a docset bundle that is currently being viewed on an AppleTV over AirPlay
- Switch from 3G to Wifi or Airplane Mode while viewing documentation
- Search for empty whitespace on a 1st-gen iPad
And so forth. Essentially any circumstance I’ve ever run up against that has crashed my app during the course of development I re-test manually before releasing my app. I’ll prioritize the list based on recently-added or -changed features, but I’ll still run through the list at least once before uploading a release. This is a good thing to do while you’re waiting for your localizations to be finished.
Manage your Jenkins builds
It goes without saying that you should be using some sort of continuous integration on your project to generate your builds and to monitor the quality of your apps over time. If you aren’t convinced, check out my very first blog post on automating iOS app builds since it’s still very relevant today. But at a minimum you should make sure your build script archives the dSYM files for your finished build in Jenkins so that you’ll be able to properly symbolicate crash logs sent to you from your users (if you’re so lucky). Without it you’ll be flying blind when trying to read a crash log.
Additionally, when you decide on a particular build that will become your released app, you should mark the build in Jenkins with the appropriate version number, and mark the build as “Keep this build forever”. This will ensure you always have access to your old builds in the event you need to perform upgrade testing from old versions, or to track down bugs in older versions of your app. You’ll notice in the screenshot of my build history for Docset Viewer, instead of showing the build number it shows the app version number, as well as a “lock” icon indicating that those builds will never be accidentally deleted.
Finally, here’s the full checklist I use to make sure I release my apps consistently, and with quality. It’s always changing, and sometimes I skip steps if they aren’t needed, but I use it as a guide to make sure I’m on-track and don’t forget any steps. Good luck!
- Revise Description.txt and Updates.txt;
- Generate localization strings (Localizable.strings, InfoPlist.strings, Root~iphone.strings, Root~ipad.strings);
- Send localizations to iCanLocalize;
- Perform manual test cases (on different generations of device) & get beta-tester feedback;
- Take new screenshots in English (if necessary);
- Register a new version in iTunes Connect; update description, updates text, screenshots, app icon as needed.
After localizations are complete:
- Download & update localizations;
- Take new screenshots in each language (if necessary);
- Sanity-check the app in each language to ensure labels fit;
- Update localizations in iTunes Connect; new updates text, description, and screenshots as needed;
- Upload new app IPA to iTunes Connect;
- Mark the build in Jenkins as v1.xRC (Release Candidate).
After app accepted by iTunes Connect:
- Mark the build in Jenkins as “v1.x” and lock the build so it can’t be deleted;
- Checkout that revision in git and tag it with the appropriate release version number;
- Go out for a pint to celebrate!