Quick Note: This post is about increasing version number visibility in iOS, but Android projects would benefit from a similar approach!

I can’t tell you the number of times I’ve had this kind of conversation when trying to process a bug report:

Customer: Hey, the app isn’t [doing the thing it’s supposed to do].

Me: That’s a problem! Let me try to reproduce it.

Me: cannot reproduce the problem

Me: Huh. That’s weird. What version are you on?

Customer: silence

Customer: How would I figure that out?

Me: silence

This is one example that demonstrates why you need to make your version information accessible in as many places as possible. If you haven’t provided your users multiple ways to identify what version they’re on, you can get stuck quickly.

Knowing the version number can help with many different situations:

If a bug has been fixed in a particular version, you can stop triaging a reported issue immediately if you can tell that the user is on an older version.

If a user reports an issue and they can demonstrate conclusively that they’re on the latest version, you don’t have to waste time having them reinstall the app.

If a bug has been reported and you have multiple versions that are active, being able to identify the version number on a device can help you deterimine which specific version introduced the bug: “Look, it worked fine here in 1.5.2, but 1.5.3 is broken.”

When presenting a demo, knowing absolutely that the demo device is running the correct version (which may not be the latest) instills confidence and improves the chances of a successful demo.

If the QA team routinely adds screenshots of the version they’re testing to bug reports, it reduces uncertainty about the reliability of a test result.

If a user swears that they’re on the latest version, but they’ve been incorrect before and you need to verify the version number, being able to request a screenshot or a verbal confirmation increases confidence in the report.

If your app crashes on launch, a user isn’t going to be able to look in a Settings menu to tell you what version it is. Showing the version number on the launch screen storyboard is a great way to solve this problem:

Note that the launch storyboard can’t be changed dynamically when your app launches. But what you can do is dynamically update a static label on your LaunchScreen.storyboard as you create your build through a little scripting and a custom Build Phase.

Start by adding a label to your LaunchScreen.storyboard :

Format the label however you like. I like to use something subtle that you can read when you need to but that isn’t very noticeable otherwise. Set Document/Label to APP_VERSION . You will use this value in the Build Phase script.

Continue by adding the custom Build Phase:

Select your project in the upper-left-hand corner of Xcode. Select the app target towards the middle of the screen.

You should see a strip of tabs that includes General , Capabilities , […], Build Settings , and then Build Phases .

Click on the + above the list of build phases and choose New Run Script Phase Rename the phase to something like “Show Version on LaunchScreen”. Xcode can be finicky about this part; if you can’t rename the phase, don’t worry too much about it. Move the phase somewhere above Copy Bundle Resources . Paste the following into the script area:

# Output version & build number to the APP_VERSION label on LaunchScreen.storyboard versionNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "${INFOPLIST_FILE}") buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${INFOPLIST_FILE}") sed -i "" -e "/userLabel=\"APP_VERSION\"/s/text=\"[^\"]*\"/text=\"Version: $versionNumber ($buildNumber)\"/" "$PROJECT_DIR/$PROJECT_NAME/Storyboards/Base.lproj/LaunchScreen.storyboard"

Note that the path in the sed command must match the path to your LaunchScreen.storyboard. In this project, it’s saved under the main project folder in a folder called Storyboards .

This is the spot that you will most likely direct your user to when they ask the question, “How do I know what version I’m on?”

You want it to be easy to navigate to, because you will either be writing instructions to someone in Slack or in an email, or talking them through it on the phone.

Other than that, there aren’t any guidelines. Just be sure you report all the information you need:

Version number (major, minor, and build)

Any special environment information (dev, staging, production)

Anything else that’s unique to your application that will help you when triaging issues

Log your version information to the console in AppDelegate.swift , at the very beginning of application(:didFinishLaunchingWithOptions:) .

It’s good practice to log your version info to the Console on launch. Why? A few reasons.

If you don’t implement the storyboard custom Build Phase, or if your app loads so quickly that the user can’t see what it says, this is yet another way to identify the version number on launch. If you connect your device to your Mac, you can use the Console.app to see the log message as it goes by. If you’re filtering a bunch of messages from the log, having a phrase you can search for is very helpful. When you’re developing, seeing that version information scroll by in Xcode each time you launch your app can be incredibly helpful. Personally, I’ve caught myself multiple times either testing the wrong version or seeing that I was in the wrong environment before taking in-app actions I would have regretted.

func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { print("\(Bundle.main.versionString) (\(Bundle.main.bundleID))") [...] return true }

This is tangential to the above, in that it’s not something your users will see, but if you use the version number as your git commit message for Info.plist , it makes it incredibly easy to visually scroll through your commit history and identify what features and fixes were present in a particular version.

Extensions to retrieve and format version information:

import Foundation extension Bundle { private var releaseVersionNumber: String { return infoDictionary?["CFBundleShortVersionString"] as? String ?? "" } private var buildVersionNumber: String { return infoDictionary?["CFBundleVersion"] as? String ?? "" } var bundleID: String { return Bundle.main.bundleIdentifier?.lowercased() ?? "" } var versionString: String { var scheme = "" // If you use different bundle IDs for different environments, code like this is helpful: if bundleID.contains(".dev") { scheme = "Dev" } else if bundleID.contains(".staging") { scheme = "Staging" } let returnValue = "Version \(releaseVersionNumber).\(buildVersionNumber) \(scheme)" return returnValue.trimmingCharacters(in: .whitespacesAndNewlines) } }

Log the version info in AppDelegate.swift :

func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { print("\(Bundle.main.versionString) (\(Bundle.main.bundleID))") [...] return true }

Of course! A working example of this post is in a repository in GitHub that will continue to evolve and be referenced by multiple posts.

Get the code that corresponds to this post: Release 2019-06-26

Thanks for reading, everyone!

May this help you and your users in all your projects.