It is a universally recognized best practice to visibly expose your iOS application’s version number somewhere on the screen. Often times you will see it on the application’s splash screen or can get to it by selecting the About menu item (assuming the application has a menu). No matter where it’s location (I have my preference as to where it should be, which I will describe momentarily), it’s a good idea to have it easily viewed by both your beta testers and your users.
To be clear, in this post we’re not going to talk about techniques for automatically incrementing the version or build numbers, or strategies for injecting git commit-ish
or branch identifiers into your application. There are plenty of resources on these topics on the Web. What we will discuss are the differences between CFShortVersionString
and CFBundleVersion
and how you can combine the two to provide a straightforward and adequate version number for your application.
I have always used the major.minor.build version number format, where major.minor are typically referred to as marketing versions, because they are heavily influenced by marketing departments and their desire to articulate to customers “how much new content” is in a given version compared to a previous one. For example, it’s pretty safe to assume that version 2.0 of some given software has a lot of new features compared to version 1.0. It’s also pretty safe to assume that version 1.1 is not radically different from version 1.0.
So we can agree that the marketing and product management teams are walking around talking about version major.minor, and that’s fine. For the purposes of distributing and tracking versions of our application, however, we need one more field, the build identifier. Ideally, this number should always be increasing, and no two different builds that are distributed to beta testers should have the same build number. (Note: we say “ideally” but in reality the CFBundleVersion
, which we will use as our build identifier, must be incremented when posting a new version to iTunesConnect).
It turns out that our Xcode target comes with Identity settings such as Bundle Identifier, Version, and Build. We will leverage those settings, and in particular refer to the combination of Version and Build as our application version.
The Xcode Version is prepopulated with 1.0
for a new project, and this will serve as our major.minor
marketing version. It also happens to be the version number that people will see on the iTunes store. The Xcode Build version will be the build
value in our major.minor.build
tuple. Again, let’s take a moment to stress that for this post we aren’t going to talk about how to automatically increment that value and will increment it by hand every time we build a version that we want to distribute.
Now that we know what values we want to treat as major.minor.build
let’s look at how we can access them in our application. It turns out these two values have names in our application’s main bundle, which is loaded from the contents of the applications Info.plist
. We can extract the values by referring to them by their names: CFBundleShortVersionString
and CFBundleVersion
. Here’s the Swift code for doing so, and it should be placed in your application(application:didFinishLaunchingWithOptions:)
routine in the AppDelegate
class.
[objc]
let appInfo = NSBundle.mainBundle().infoDictionary as Dictionary<String,AnyObject>
let shortVersionString = appInfo["CFBundleShortVersionString"] as String
let bundleVersion = appInfo["CFBundleVersion"] as String
[/objc]
Note: We’re leveraging Swift’s as
keyword, which allows us to specify that appInfo
is a Dictionary
of AnyObject
s with keys of type String
. Furthermore, we apply as
again to tell Swift that the objects we are retrieving for the keys CFBundleShortVersionString
and CFBundleVersion
are String
s. Don’t make the mistake in thinking that our appInfo
dictionary consists of only of String-String
key-value pairs. Refer to Apple’s documentation for details of the types of values stored.
We can combine the CFBundleShortVersionString
and CFBundleVersion
together as:
[objc]
let applicationVersion = shortVersionString + "." + bundleVersion
[/objc]
Okay, now that we’ve established we can create a version string from CFBundleShortVersionString
and CFBundleVersion
, where should we display it? I am a fan of placing it in the application’s iOS Settings page. This is page created automatically by iOS for your application, and it can be found by going to the iOS Settings application and scrolling down to the bottom of the default page. For complete details of an application settings page, see Apple’s documentation.
To create a settings page you will need to create a Settings Bundle for your application. In Xcode select File – New – File and when presented with choosing a template for your new file, select iOS – Resource – Settings Bundle, like so:
Select Next and ensure that your settings bundle is included in your application target (it should be by default). Click Create and you will see Xcode has included Settings.bundle
in your project (I also place my Settings.bundle
in the Supporting Files group):
We are interested in the file called Root.plist
, where we will add a Group section called About, which will contain both our copyright line as well as version number. Click on Root.plist
to bring up the default contents:
Delete Item 1, Item 2, and Item 3 by clicking on the row for the item to highlight it, and then right-click and choose Cut. When you delete one item the remaining items will renumber themselves, so simply ensure only one item remains, Item 0, which by default is a Group.
Click on the disclosure triangle for Item 0 and you will see Title and Type. Leave the Type as it is (a Group), and change the title to About. Your panel should look like this:
Now for the trickier part, and it’s only trickier because the Xcode plist editor can be a pain. Click on the Preference Items row and right-click and select Add Row. You will probably notice that our Group got moved to Item 1. We don’t want that, so click and drag the Item 1 row back to be the first entry. Add another row, and again, rearrange the entries such that our About group is the first. It should look something like this:
Now, let’s set the two fields. The first will be our copyright statement. Click on the disclosure triangle for Item 1, and then right-click on Item 1 and Add Row. The default row should have Default Value as its type. Put your copyright statement as the default value, change the title field to Copyright, and then for Identifier put application_copyright
.
The second field will be our application version number. Click on the disclosure triangle for Item 2, and then right-click on Item 2 and Add Row. Set the default value to 0.0.0, change the title field to Version, and then for Identifier put application_version
.
Your settings Root.plist
should now look like
If you ran the application as is and then navigated to the iOS Settings page for the application, you would see your copyright statement and 0.0.0 as the version number. Of course we want to get the actual version number in there. Here’s the code for doing so (again, place it after your extract the values from appInfo
in your application delegate’s application(application:didFinishLaunchingWithOptions)
routine):
[objc]
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(applicationVersion, forKey: "application_version")
defaults.synchronize()
[/objc]
NSUserDefaults.standardUserDefaults()
gives us access to our application’s settings, to which we write our applicationVersion
to the entry given by the key application_version
. defaults.synchronize()
persists the updated information to storage such that other applications (such as iOS Settings) can retrieve the value.
Run the application again, browse to the iOS Settings for the application, and find
To increment your build number, go to your project target and simply change it, rebuild the application, and see the new number available in the settings page.
Again, we’ve tiptoed around the topic of how and when to update your build version. In the past I’ve written elaborate scripts which ran under CruiseControl, extracted the SVN version number, injected that number into the Info.plist
using PListBuddy, generated fancy build records that could be used independently to package other components together, etc. In the end I found that it was often more trouble than it was worth, and if you are running a development team with only a handful of folks, simply appoint one to be responsible for building the version that gets tested and eventually posted to Apple for review. They can either build the application by hand (using Xcode) or develop automated tools for it; the important thing is the information is unique for each build distributed and can easily be viewed!