How to Read the App Version and Build Number in Swift

How to Read the App Version and Build Number in Swift

Written by

in

Quick answer: To get the app version and build number in Swift, read the CFBundleShortVersionString and CFBundleVersion keys from Bundle.main.infoDictionary. Wrap these lookups in a Bundle extension with optional casting (as? String) to safely access the values without force unwrapping.

Why this happens

Apple separates application release metadata into two distinct fields inside your app target’s Info.plist file: the marketing version and the internal build number. In the raw property list, these correspond to the keys CFBundleShortVersionString and CFBundleVersion.

The marketing version (CFBundleShortVersionString) represents the user-facing release string visible on the App Store (such as 2.4.1). The build number (CFBundleVersion) represents the machine-readable iteration compiled by Xcode or CI/CD pipelines (such as 108 or 20261109.1). App Store Connect requires the marketing version to follow standard semantic versioning and mandates that every uploaded build number increments sequentially.

At runtime, iOS stores these values in Bundle.main.infoDictionary. Because infoDictionary returns an optional dictionary typed as [String: Any]?, developers often encounter runtime crashes by force-unwrapping the dictionary or force-casting the values. Accessing these strings safely requires defensive unwrapping and handling multi-bundle targets properly when code runs inside frameworks or Swift Packages.

How to fix it

1. Extend Bundle with Safe Computed Properties (Recommended)

The cleanest approach creates computed properties directly on Bundle. This keeps your call sites readable while guaranteeing that missing keys or unexpected types fall back gracefully rather than crashing.

import Foundation

extension Bundle {
    /// The user-facing marketing version (e.g., "1.0.4").
    var releaseVersionNumber: String? {
        return infoDictionary?["CFBundleShortVersionString"] as? String
    }

    /// The internal build iteration number (e.g., "42").
    var buildVersionNumber: String? {
        return infoDictionary?["CFBundleVersion"] as? String
    }

    /// A formatted string combining both values (e.g., "1.0.4 (42)").
    var fullAppVersion: String {
        guard let version = releaseVersionNumber else {
            return "Version Unavailable"
        }
        if let build = buildVersionNumber {
            return "v\(version) (\(build))"
        }
        return "v\(version)"
    }
}

You can now access these properties anywhere across your application:

let version = Bundle.main.releaseVersionNumber ?? "0.0.0"
let build = Bundle.main.buildVersionNumber ?? "0"
let debugHeader = Bundle.main.fullAppVersion

print("Running \(debugHeader)")

2. Display the Version in a SwiftUI Settings Screen

When presenting version details to users on an “About” or “Settings” screen, bind the extension directly to your SwiftUI view hierarchy. Providing a fallback string ensures Xcode Previews and unit tests render cleanly even if the mock environment lacks a fully populated bundle dictionary.

import SwiftUI

struct AboutAppView: View {
    private var appVersionDisplay: String {
        Bundle.main.fullAppVersion
    }

    var body: some View {
        List {
            Section(header: Text("Application Details")) {
                HStack {
                    Text("Version")
                    Spacer()
                    Text(appVersionDisplay)
                        .foregroundColor(.secondary)
                }
            }
        }
    }
}

#Preview {
    AboutAppView()
}

3. Read Version Information from Frameworks and Swift Packages

If your telemetry, networking, or diagnostic logger lives inside a modularized dynamic framework or a Swift Package, querying Bundle.main returns the metadata of the host application, not the framework. Depending on your objective, you must choose the correct bundle instance.

To identify the host application’s version from inside a framework, continue using Bundle.main:

// Retrieves the version of the main iOS app hosting this framework
let hostAppVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String

To identify the framework’s own version, locate the bundle associated with a class inside that module:

public final class AnalyticsEngine {
    public static var frameworkVersion: String? {
        let frameworkBundle = Bundle(for: AnalyticsEngine.self)
        return frameworkBundle.infoDictionary?["CFBundleShortVersionString"] as? String
    }
}

For Swift Packages that define resources, Swift synthesizes Bundle.module automatically:

let packageVersion = Bundle.module.infoDictionary?["CFBundleShortVersionString"] as? String

4. Include App Version in Network Headers and Crash Reports

A primary use case for reading the version string is attaching user-agent headers to outgoing HTTP requests or annotating structured logs.

import Foundation

struct APIClient {
    static func makeUserAgent() -> String {
        let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "App"
        let version = Bundle.main.releaseVersionNumber ?? "UnknownVersion"
        let build = Bundle.main.buildVersionNumber ?? "UnknownBuild"
        let osVersion = ProcessInfo.processInfo.operatingSystemVersionString

        return "\(appName)/\(version) (\(build); iOS \(osVersion))"
    }
}

How to prevent it

  • Never force-unwrap Bundle.main.infoDictionary with !. While standard app targets always produce a dictionary, custom test runners, extensions, and command-line targets can return nil.
  • Do not confuse CFBundleShortVersionString with CFBundleVersion. Use the short string for public-facing UIs and analytics dashboards, and use the build number for build tracking and bug reproduction.
  • Avoid hardcoding version strings in Swift source files. Always let Xcode manage the values through the target’s build settings (MARKETING_VERSION and CURRENT_PROJECT_VERSION).
  • Verify that your test host target includes a valid Info.plist if your unit tests execute assertions against bundle version helpers.

FAQ

What is the difference between CFBundleShortVersionString and CFBundleVersion?

CFBundleShortVersionString corresponds to the “Marketing Version” displayed to users on the App Store. CFBundleVersion represents the internal build number used by App Store Connect and developers to distinguish between consecutive uploads of the same release.

Why does Bundle.main.infoDictionary return nil in unit tests?

When unit tests run without a configured host application, Bundle.main resolves to the test runner bundle rather than your application bundle. Set the “Host Application” dropdown in your test target’s General settings to your app target to restore access to the app’s Info.plist.

Can users modify the app version and build strings at runtime?

No. The Info.plist file resides inside the signed .app bundle directory. iOS enforces code signing across the entire bundle, meaning any modification to files within the bundle invalidates the signature and prevents the app from launching.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 3.0; this article is shared under the same license.