Post

Detect If Your iOS Device Is Running a Beta Version

Learn how to detect if iOS is running a beta, which type of beta (developer, public, RC), and get the full build information.

Detect If Your iOS Device Is Running a Beta Version

Why Detect iOS Beta Versions?

When your app runs on iOS beta versions, you might want to:

  • Enable additional logging for debugging
  • Report beta-specific crashes separately
  • Disable features that break on pre-release iOS
  • Show beta testers which build they’re testing

The Problem: No Official Beta API

Apple doesn’t provide an isBeta property. The UIDevice.systemVersion returns “18.0” for both beta and release versions. However, we can detect beta builds using ProcessInfo.

Solution: Complete Beta Detection System

Here’s a comprehensive solution that detects if iOS is running a beta and identifies the type:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import Foundation
import UIKit

enum iOSBetaType: String {
    case developerBeta = "Developer Beta"
    case publicBeta = "Public Beta"
    case releaseCandidate = "Release Candidate"
    case seed = "Seed Build"
    case stable = "Stable Release"
}

struct iOSBetaDetector {

    // Get the full version string with build info
    static var fullVersionString: String {
        ProcessInfo.processInfo.operatingSystemVersionString
    }

    // Get just the iOS version number
    static var versionNumber: String {
        UIDevice.current.systemVersion
    }

    // Check if device is running any beta version
    static var isRunningBeta: Bool {
        detectBetaType() != .stable
    }

    // Detect which type of beta (if any)
    static func detectBetaType() -> iOSBetaType {
        let versionString = fullVersionString.lowercased()

        // Check for explicit beta markers
        if versionString.contains("release candidate") ||
           versionString.contains(" rc ") {
            return .releaseCandidate
        }

        if versionString.contains("developer beta") {
            return .developerBeta
        }

        if versionString.contains("public beta") ||
           versionString.contains("beta") {
            return .publicBeta
        }

        if versionString.contains("seed") {
            return .seed
        }

        // Check build number pattern
        if hasBetaBuildSuffix() {
            return .developerBeta // Builds with letter suffix are typically dev betas
        }

        return .stable
    }

    // Get build number from version string
    static var buildNumber: String? {
        let pattern = "Build\\s+([A-Z0-9a-z]+)"
        guard let regex = try? NSRegularExpression(pattern: pattern) else { return nil }

        let range = NSRange(location: 0, length: fullVersionString.utf16.count)
        guard let match = regex.firstMatch(in: fullVersionString, range: range),
              let buildRange = Range(match.range(at: 1), in: fullVersionString) else {
            return nil
        }

        return String(fullVersionString[buildRange])
    }

    // Beta builds have letter suffix: 22A5282m (beta) vs 22A5282 (release)
    private static func hasBetaBuildSuffix() -> Bool {
        guard let build = buildNumber else { return false }

        // Check if last character is a lowercase letter
        if let lastChar = build.last, lastChar.isLowercase {
            return true
        }

        return false
    }

    // Get human-readable beta information
    static func getBetaInfo() -> String {
        let betaType = detectBetaType()
        let version = versionNumber
        let build = buildNumber ?? "Unknown"

        switch betaType {
        case .stable:
            return "iOS \(version) (\(build)) - Stable Release"
        default:
            return "iOS \(version) (\(build)) - \(betaType.rawValue)"
        }
    }
}

Detecting TestFlight App Betas

If you also need to detect if your app is running via TestFlight:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
enum AppConfiguration {
    case debug
    case testFlight
    case appStore
}

struct AppEnvironment {
    private static let isTestFlight = Bundle.main.appStoreReceiptURL?
        .lastPathComponent == "sandboxReceipt"

    static var isDebug: Bool {
        #if DEBUG
        return true
        #else
        return false
        #endif
    }

    static var configuration: AppConfiguration {
        if isDebug {
            return .debug
        } else if isTestFlight {
            return .testFlight
        } else {
            return .appStore
        }
    }
}

Practical Usage Examples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Check if running on beta iOS
if iOSBetaDetector.isRunningBeta {
    print("Device is running a beta iOS version")

    // Get specific beta type
    let betaType = iOSBetaDetector.detectBetaType()
    switch betaType {
    case .developerBeta:
        print("Running Developer Beta")
        enableDeveloperFeatures()

    case .publicBeta:
        print("Running Public Beta")
        enablePublicBetaFeatures()

    case .releaseCandidate:
        print("Running Release Candidate")
        // Almost stable, minimal changes

    case .seed:
        print("Running Seed Build")
        enableInternalTestingFeatures()

    case .stable:
        print("Running Stable iOS")
    }
}

// Display version info to users (useful for beta testers)
let betaInfo = iOSBetaDetector.getBetaInfo()
print(betaInfo)
// Output: "iOS 18.0 (22A5282m) - Developer Beta"

// Get specific build number
if let build = iOSBetaDetector.buildNumber {
    analytics.log(event: "app_launched", parameters: ["ios_build": build])
}

// Check app distribution method
switch AppEnvironment.configuration {
case .debug:
    print("Running from Xcode")
case .testFlight:
    print("Distributed via TestFlight")
case .appStore:
    print("Downloaded from App Store")
}

How Beta Detection Works

  1. Version String Analysis: The ProcessInfo.operatingSystemVersionString contains detailed build information:
    • Developer Beta: "Version 18.0 (Build 22A5282m)" - Note the ‘m’ suffix
    • Public Beta: "Version 18.0 (Build 22A5282b)" - ‘b’ suffix
    • Release Candidate: "Version 18.0 (Build 22A5282) RC"
    • Stable Release: "Version 18.0 (Build 22A5282)" - No suffix
  2. Build Number Pattern: Beta builds append a lowercase letter to the build number. The letter often indicates:
    • a, b, c: Early betas
    • m, n, o: Mid-cycle betas
    • No letter: Stable release

Important Notes

  • Cache Results: Store beta detection results at app launch to avoid repeated regex processing
  • Not 100% Reliable: This relies on Apple’s naming conventions which could change
  • Test on Real Betas: Always verify during each iOS beta cycle
  • Use for Diagnostics: Best used for logging and debugging, not critical app logic

☕ Support My Work

If you found this post helpful and want to support more content like this, you can buy me a coffee!

Your support helps me continue creating useful articles and tips for fellow developers. Thank you! 🙏

This post is licensed under CC BY 4.0 by the author.