94

I have two different app version Strings (i.e. "3.0.1" and "3.0.2").

How can compare these using Swift?

fpg1503
  • 7,492
  • 6
  • 29
  • 49
codeman
  • 8,868
  • 13
  • 50
  • 79
  • you can check out my answer on a similar question: https://stackoverflow.com/a/55826192/611879 – budiDino Nov 26 '21 at 10:32
  • Better to use Swift native type `OperatingSystemVersion` [Compare app versions after update using decimals like 2.5.2](https://stackoverflow.com/a/70964516/2303865) – Leo Dabus Mar 09 '22 at 13:27

20 Answers20

154

Ended up having to convert my Strings to NSStrings:

if storeVersion.compare(currentVersion, options: NSStringCompareOptions.NumericSearch) == NSComparisonResult.OrderedDescending {
       println("store version is newer")
}

Swift 3

let currentVersion = "3.0.1"
let storeVersion = "3.0.2"

if storeVersion.compare(currentVersion, options: .numeric) == .orderedDescending {
    print("store version is newer")
}
Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
codeman
  • 8,868
  • 13
  • 50
  • 79
  • 1
    Or you could write an extension on the swift-string which is a really fancy way of doing it. – Simon Jan 14 '15 at 01:05
  • @Simon Thanks...I did something like this: – James Laurenstin Aug 06 '16 at 10:09
  • 2
    `extension String { func isVersionNewer(compareVersion: String) -> Bool { if self.compare(compareVersion, options: NSStringCompareOptions.NumericSearch) == NSComparisonResult.OrderedDescending { return true } return false }` – James Laurenstin Aug 06 '16 at 10:09
  • 2
    Swift 3 version: `if storeVersion.compare(currentVersion, options: NSString.CompareOptions.numeric) == ComparisonResult.orderedDescending { NSLog("store version is newer") }` – Chad Feb 22 '17 at 22:59
  • 4
    Important note: if you decide to check whether version numbers are equal or newer, using .orderedSame, beware of version numbers ending in .0. Comparing 2.4 to 2.4.0 will return true in one direction, but false in the other. Best thing to do is to avoid having versions ending in .0. Second best thing is to trim version numbers of their potential ".0" before comparing them. – Kqtr Aug 25 '17 at 16:49
  • I think you mean to use .orderedAscending when comparing storeVersion to currentVersion. – 6rchid Nov 21 '20 at 08:59
  • It will fail if you forget to specify decimal value e.x. "15.0" and "15". – Raymond Mar 09 '22 at 10:34
50

You don't need to cast it as NSString. String object in Swift 3 is just powerful enough to compare versions like below.

let version = "1.0.0"
let targetVersion = "0.5.0"

version.compare(targetVersion, options: .numeric) == .orderedSame        // false
version.compare(targetVersion, options: .numeric) == .orderedAscending   // false
version.compare(targetVersion, options: .numeric) == .orderedDescending  // true

But sample above does not cover versions with extra zeros.(Ex: "1.0.0" & "1.0")

So, I've made all kinds of these extension methods in String to handle version comparison using Swift. It does consider extra zeros I said, quite simple and will work as you expected.

XCTAssertTrue(UIDevice.current.systemVersion.isVersion(lessThan: "99.0.0"))
XCTAssertTrue(UIDevice.current.systemVersion.isVersion(equalTo: UIDevice.current.systemVersion))
XCTAssertTrue(UIDevice.current.systemVersion.isVersion(greaterThan: "3.5.99"))
XCTAssertTrue(UIDevice.current.systemVersion.isVersion(lessThanOrEqualTo: "13.5.99"))
XCTAssertTrue(UIDevice.current.systemVersion.isVersion(greaterThanOrEqualTo: UIDevice.current.systemVersion))
XCTAssertTrue("0.1.1".isVersion(greaterThan: "0.1"))
XCTAssertTrue("0.1.0".isVersion(equalTo: "0.1"))
XCTAssertTrue("10.0.0".isVersion(equalTo: "10"))
XCTAssertTrue("10.0.1".isVersion(equalTo: "10.0.1"))
XCTAssertTrue("5.10.10".isVersion(lessThan: "5.11.5"))
XCTAssertTrue("1.0.0".isVersion(greaterThan: "0.99.100"))
XCTAssertTrue("0.5.3".isVersion(lessThanOrEqualTo: "1.0.0"))
XCTAssertTrue("0.5.29".isVersion(greaterThanOrEqualTo: "0.5.3"))

Just take a look and take all you want in my sample extension repository with no license to care about.

https://github.com/DragonCherry/VersionCompare

DragonCherry
  • 742
  • 7
  • 10
  • 3
    Perfect approach, @DragonCherry. This should be considered the correct answer. – Leandro Fournier Aug 31 '18 at 20:12
  • 2
    Your extension is great! But, unfortunately, it doesn't work when the version is like `1.0.1.2` (I know, it should exist, but things happens). I changed your extension and improved the tests to cover (I believe) all cases. Also, I created an extension that removes all the unnecessary characters from the version string, sometimes it can be `v1.0.1.2`. You can check all the code in the following gists: String extension - https://gist.github.com/endy-s/3791fe5c856cccaabff331fd49356dbf Tests - https://gist.github.com/endy-s/7cacaa730bf9fd5abf6021e58e962191 Hope it helps anyone :) – Endy Silveira May 23 '19 at 18:42
  • 1
    @EndySilveira It's even better idea! But I'd rather apply that logic before use given version methods, separately. Of course it's my personal opinion. Thank you for your advice. – DragonCherry Jul 03 '20 at 08:17
28

Swift 4+

let current = "1.3"
let appStore = "1.2.9"
let versionCompare = current.compare(appStore, options: .numeric)
if versionCompare == .orderedSame {
    print("same version")
} else if versionCompare == .orderedAscending {
    // will execute the code here
    print("ask user to update")
} else if versionCompare == .orderedDescending {
    // execute if current > appStore
    print("don't expect happen...")
}
PinkeshGjr
  • 8,460
  • 5
  • 41
  • 56
22

Swift 3 version

let storeVersion = "3.14.10"
let currentVersion = "3.130.10"

extension String {
    func versionToInt() -> [Int] {
        return self.components(separatedBy: ".")
            .map { Int.init($0) ?? 0 }
    }
}
//true
storeVersion.versionToInt().lexicographicallyPrecedes(currentVersion.versionToInt())

Swift 2 version compare

let storeVersion = "3.14.10"

let currentVersion = "3.130.10"
extension String {
    func versionToInt() -> [Int] {
      return self.componentsSeparatedByString(".")
          .map {
              Int.init($0) ?? 0
          }
    }
}

// true
storeVersion.versionToInt().lexicographicalCompare(currentVersion.versionToInt()) 
Wane
  • 301
  • 3
  • 5
13

The following is working for me:

extension String {

  static func ==(lhs: String, rhs: String) -> Bool {
    return lhs.compare(rhs, options: .numeric) == .orderedSame
  }

  static func <(lhs: String, rhs: String) -> Bool {
    return lhs.compare(rhs, options: .numeric) == .orderedAscending
  }

  static func <=(lhs: String, rhs: String) -> Bool {
    return lhs.compare(rhs, options: .numeric) == .orderedAscending || lhs.compare(rhs, options: .numeric) == .orderedSame
  }

  static func >(lhs: String, rhs: String) -> Bool {
    return lhs.compare(rhs, options: .numeric) == .orderedDescending
  }

  static func >=(lhs: String, rhs: String) -> Bool {
    return lhs.compare(rhs, options: .numeric) == .orderedDescending || lhs.compare(rhs, options: .numeric) == .orderedSame
  }

}


"1.2.3" == "1.2.3" // true
"1.2.3" > "1.2.3" // false
"1.2.3" >= "1.2.3" // true
"1.2.3" < "1.2.3" // false
"1.2.3" <= "1.2.3" // true

"3.0.0" >= "3.0.0.1" // false
"3.0.0" > "3.0.0.1" // false
"3.0.0" <= "3.0.0.1" // true
"3.0.0.1" >= "3.0.0.1" // true
"3.0.1.1.1.1" >= "3.0.2" // false
"3.0.15" > "3.0.1.1.1.1" // true
"3.0.10" > "3.0.100.1.1.1" // false
"3.0.1.1.1.3.1.7" == "3.0.1.1.1.3.1" // false
"3.0.1.1.1.3.1.7" > "3.0.1.1.1.3.1" // true

"3.14.10" == "3.130.10" // false
"3.14.10" > "3.130.10" // false
"3.14.10" >= "3.130.10" // false
"3.14.10" < "3.130.10" // true
"3.14.10" <= "3.130.10" // true

enter image description here

Ashok
  • 5,585
  • 5
  • 52
  • 80
  • 1
    A problem with this approach is that "1.0" < "1.0.0" will be true. DragonCherry's approach doesn't have that problem. – ThomasW Sep 04 '18 at 03:58
5

I have mixed the Ashok version and DragonCherry:

// MARK: - Version comparison

extension String {

    // Modified from the DragonCherry extension - https://github.com/DragonCherry/VersionCompare
    private func compare(toVersion targetVersion: String) -> ComparisonResult {
        let versionDelimiter = "."
        var result: ComparisonResult = .orderedSame
        var versionComponents = components(separatedBy: versionDelimiter)
        var targetComponents = targetVersion.components(separatedBy: versionDelimiter)

        while versionComponents.count < targetComponents.count {
            versionComponents.append("0")
        }

        while targetComponents.count < versionComponents.count {
            targetComponents.append("0")
        }

        for (version, target) in zip(versionComponents, targetComponents) {
            result = version.compare(target, options: .numeric)
            if result != .orderedSame {
                break
            }
        }

        return result
    }

    func isVersion(equalTo targetVersion: String) -> Bool { return compare(toVersion: targetVersion) == .orderedSame }

    func isVersion(greaterThan targetVersion: String) -> Bool { return compare(toVersion: targetVersion) == .orderedDescending }

    func isVersion(greaterThanOrEqualTo targetVersion: String) -> Bool { return compare(toVersion: targetVersion) != .orderedAscending }

    func isVersion(lessThan targetVersion: String) -> Bool { return compare(toVersion: targetVersion) == .orderedAscending }

    func isVersion(lessThanOrEqualTo targetVersion: String) -> Bool { return compare(toVersion: targetVersion) != .orderedDescending }

    static func ==(lhs: String, rhs: String) -> Bool { lhs.compare(toVersion: rhs) == .orderedSame }

    static func <(lhs: String, rhs: String) -> Bool { lhs.compare(toVersion: rhs) == .orderedAscending }

    static func <=(lhs: String, rhs: String) -> Bool { lhs.compare(toVersion: rhs) != .orderedDescending }

    static func >(lhs: String, rhs: String) -> Bool { lhs.compare(toVersion: rhs) == .orderedDescending }

    static func >=(lhs: String, rhs: String) -> Bool { lhs.compare(toVersion: rhs) != .orderedAscending }

}

Using:

"1.2.3" == "1.2.3" // true
"1.2.3" > "1.2.3" // false
"1.2.3" >= "1.2.3" // true
"1.2.3" < "1.2.3" // false
"1.2.3" <= "1.2.3" // true

"3.0.0" >= "3.0.0.1" // false
"3.0.0" > "3.0.0.1" // false
"3.0.0" <= "3.0.0.1" // true
"3.0.0.1" >= "3.0.0.1" // true
"3.0.1.1.1.1" >= "3.0.2" // false
"3.0.15" > "3.0.1.1.1.1" // true
"3.0.10" > "3.0.100.1.1.1" // false
"3.0.1.1.1.3.1.7" == "3.0.1.1.1.3.1" // false
"3.0.1.1.1.3.1.7" > "3.0.1.1.1.3.1" // true

"3.14.10" == "3.130.10" // false
"3.14.10" > "3.130.10" // false
"3.14.10" >= "3.130.10" // false
"3.14.10" < "3.130.10" // true
"3.14.10" <= "3.130.10" // true

"0.1.1".isVersion(greaterThan: "0.1")
"0.1.0".isVersion(equalTo: "0.1")
"10.0.0".isVersion(equalTo: "10")
"10.0.1".isVersion(equalTo: "10.0.1")
"5.10.10".isVersion(lessThan: "5.11.5")
"1.0.0".isVersion(greaterThan: "0.99.100")
"0.5.3".isVersion(lessThanOrEqualTo: "1.0.0")
"0.5.29".isVersion(greaterThanOrEqualTo: "0.5.3")
Mickael Belhassen
  • 2,970
  • 1
  • 25
  • 46
4

Using Swift 3, application version strings can be compare using the compare function with the option set to numeric.

read this String Programming Guide from Apple Developers' documentation for Objective-C examples of how it works.

i tried these at https://iswift.org/playground

print("2.0.3".compare("2.0.4", options: .numeric))//orderedAscending
print("2.0.3".compare("2.0.5", options: .numeric))//orderedAscending

print("2.0.4".compare("2.0.4", options: .numeric))//orderedSame

print("2.0.4".compare("2.0.3", options: .numeric))//orderedDescending
print("2.0.5".compare("2.0.3", options: .numeric))//orderedDescending

print("2.0.10".compare("2.0.11", options: .numeric))//orderedAscending
print("2.0.10".compare("2.0.20", options: .numeric))//orderedAscending

print("2.0.0".compare("2.0.00", options: .numeric))//orderedSame
print("2.0.00".compare("2.0.0", options: .numeric))//orderedSame

print("2.0.20".compare("2.0.19", options: .numeric))//orderedDescending
print("2.0.99".compare("2.1.0", options: .numeric))//orderedAscending

Hope that helps!

If you like using libraries, use this one, don't reinvent the wheel. https://github.com/zenangst/Versions

computingfreak
  • 4,939
  • 1
  • 34
  • 51
3

Sometimes, the storeVersion's length is not equal to the currentVersion's. e.g. maybe storeVersion is 3.0.0, however, you fixed a bug and named it 3.0.0.1.

func ascendingOrSameVersion(minorVersion smallerVersion:String, largerVersion:String)->Bool{
    var result = true //default value is equal

    let smaller = split(smallerVersion){ $0 == "." }
    let larger = split(largerVersion){ $0 == "." }

    let maxLength = max(smaller.count, larger.count)

    for var i:Int = 0; i < maxLength; i++ {
        var s = i < smaller.count ? smaller[i] : "0"
        var l = i < larger.count ? larger[i] : "0"
        if s != l {
            result = s < l
            break
        }
    }
    return result
}
HamGuy
  • 916
  • 1
  • 7
  • 18
2

You can do it using 'String.compare' method. Use ComparisonResult to identify when your version greater, equal or less.

Example:

"1.1.2".compare("1.1.1").rawValue -> ComparisonResult.orderedDescending
"1.1.2".compare("1.1.2").rawValue -> ComparisonResult.orderedSame
"1.1.2".compare("1.1.3").rawValue -> ComparisonResult.orderedAscending
2

Wrote a small Swift 3 class to do this:

class VersionString: NSObject {

    // MARK: - Properties
    var string = ""
    override var description: String {
        return string
    }

    // MARK: - Initialization
    private override init() {
        super.init()
    }
    convenience init(_ string: String) {
        self.init()
        self.string = string
    }

    func compare(_ rhs: VersionString) -> ComparisonResult {
        return string.compare(rhs.string, options: .numeric)
    }

    static func ==(lhs: VersionString, rhs: VersionString) -> Bool {
        return lhs.compare(rhs) == .orderedSame
    }

    static func <(lhs: VersionString, rhs: VersionString) -> Bool {
        return lhs.compare(rhs) == .orderedAscending
    }

    static func <=(lhs: VersionString, rhs: VersionString) -> Bool {
        return lhs.compare(rhs) == .orderedAscending || lhs.compare(rhs) == .orderedSame
    }

    static func >(lhs: VersionString, rhs: VersionString) -> Bool {
        return lhs.compare(rhs) == .orderedDescending
    }

    static func >=(lhs: VersionString, rhs: VersionString) -> Bool {
        return lhs.compare(rhs) == .orderedDescending || lhs.compare(rhs) == .orderedSame
    }
}

let v1 = VersionString("1.2.3")
let v2 = VersionString("1.2.3")

print("\(v1) == \(v2): \(v1 == v2)") // 1.2.3 == 1.2.3: true
print("\(v1) >  \(v2): \(v1 > v2)")  // 1.2.3 >  1.2.3: false
print("\(v1) >= \(v2): \(v1 >= v2)") // 1.2.3 >= 1.2.3: true
print("\(v1) <  \(v2): \(v1 < v2)")  // 1.2.3 <  1.2.3: false
print("\(v1) <= \(v2): \(v1 <= v2)") // 1.2.3 <= 1.2.3: true
Alex Koshy
  • 1,613
  • 15
  • 17
2

@DragonCherry solution is great!

But, unfortunately, it doesn't work when the version is like 1.0.1.2 (I know, it shouldn't exist, but things happens).

I changed your extension and improved the tests to cover (I believe) all cases. Also, I created an extension that removes all the unnecessary characters from the version string, sometimes it can be v1.0.1.2.

You can check all the code in the following gists:

Hope it helps anyone :)

Endy Silveira
  • 211
  • 3
  • 13
2

I ended up creating below class for my project. Sharing it here, in case it helps someone. Cheers ...!!

import Foundation

final class AppVersionComparator {

var currentVersion: String

init() {
    let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
    self.currentVersion = version ?? ""
}

func compareVersions(lhs: String, rhs: String) -> ComparisonResult {
    let comparisonResult = lhs.compare(rhs, options: .numeric)
    return comparisonResult
}

/**
 - If lower bound is present perform lowerBound check
 - If upper bound is present perform upperBound check
 - Return true if both are nil
 */
func fallsInClosedRange(lowerBound: String?, upperBound: String?) -> Bool {
    if let lowerBound = lowerBound {
        let lowerBoundComparisonResult = compareVersions(lhs: currentVersion, rhs: lowerBound)
        guard lowerBoundComparisonResult == .orderedSame || lowerBoundComparisonResult == .orderedDescending else { return false }
    }
    if let upperBound = upperBound {
        let upperBoundComparisonResult = compareVersions(lhs: currentVersion, rhs: upperBound)
        guard upperBoundComparisonResult == .orderedSame || upperBoundComparisonResult == .orderedAscending else { return false }
    }
    return true
}

/**
 - If lower bound is present perform lowerBound check
 - If upper bound is present perform upperBound check
 - Return true if both are nil
 */
func fallsInOpenRange(lowerBound: String?, upperBound: String?) -> Bool {
    if let lowerBound = lowerBound {
        let lowerBoundComparisonResult = compareVersions(lhs: currentVersion, rhs: lowerBound)
        guard lowerBoundComparisonResult == .orderedDescending else { return false }
    }
    if let upperBound = upperBound {
        let upperBoundComparisonResult = compareVersions(lhs: currentVersion, rhs: upperBound)
        guard upperBoundComparisonResult == .orderedAscending else { return false }
    }
    return true
}

func isCurrentVersionGreaterThan(version: String) -> Bool {
    let result = compareVersions(lhs: currentVersion, rhs: version)
    return result == .orderedDescending
}

func isCurrentVersionLessThan(version: String) -> Bool {
    let result = compareVersions(lhs: currentVersion, rhs: version)
    return result == .orderedAscending
}

func isCurrentVersionEqualsTo(version: String) -> Bool {
    let result = compareVersions(lhs: currentVersion, rhs: version)
    return result == .orderedSame
}}

Some test cases too:

import XCTest

class AppVersionComparatorTests: XCTestCase {

var appVersionComparator: AppVersionComparator!

override func setUp() {
    super.setUp()
    self.appVersionComparator = AppVersionComparator()
}

func testInit() {
    XCTAssertFalse(appVersionComparator.currentVersion.isEmpty)
}

func testCompareEqualVersions() {
    let testVersions = [VersionComparisonModel(lhs: "1.2.1", rhs: "1.2.1"),
                        VersionComparisonModel(lhs: "1.0", rhs: "1.0"),
                        VersionComparisonModel(lhs: "1.0.2", rhs: "1.0.2"),
                        VersionComparisonModel(lhs: "0.1.1", rhs: "0.1.1"),
                        VersionComparisonModel(lhs: "3.2.1", rhs: "3.2.1")]
    for model in testVersions {
        let result = appVersionComparator.compareVersions(lhs: model.lhs, rhs: model.rhs)
        XCTAssertEqual(result, .orderedSame)
    }
}

func testLHSLessThanRHS() {
    let lhs = "1.2.0"
    let rhs = "1.2.1"
    let result = appVersionComparator.compareVersions(lhs: lhs, rhs: rhs)
    XCTAssertEqual(result, .orderedAscending)
}

func testInvalidRange() {
    let isCurrentVersionInClosedRange = appVersionComparator.currentVersionFallsInClosedRange(lowerBound: "", upperBound: "")
    XCTAssertFalse(isCurrentVersionInClosedRange)
    let isCurrentVersionInOpenRange = appVersionComparator.currentVersionFallsInOpenRange(lowerBound: "", upperBound: "")
    XCTAssertFalse(isCurrentVersionInOpenRange)
}

func testCurrentInClosedRangeSuccess() {
    appVersionComparator.currentVersion = "1.2.1"
    let isCurrentVersionInClosedRange = appVersionComparator.currentVersionFallsInClosedRange(lowerBound: "1.2.0", upperBound: "1.2.1")
    XCTAssert(isCurrentVersionInClosedRange)
}

func testIsCurrentVersionGreaterThanGivenVersion() {
    appVersionComparator.currentVersion = "1.4.2"
    let result = appVersionComparator.isCurrentVersionGreaterThan(version: "1.2")
    XCTAssert(result)
}

func testIsCurrentVersionLessThanGivenVersion() {
    appVersionComparator.currentVersion = "1.4.2"
    let result = appVersionComparator.isCurrentVersionLessThan(version: "1.5")
    XCTAssert(result)
}

func testIsCurrentVersionEqualsToGivenVersion() {
    appVersionComparator.currentVersion = "1.4.2"
    let result = appVersionComparator.isCurrentVersionEqualsTo(version: "1.4.2")
    XCTAssert(result)
}}

Mock Model:

struct VersionComparisonModel {

let lhs: String

let rhs: String 

}
Pranav Gupta
  • 741
  • 9
  • 10
1

Your use of NSString is the right way to go, but here is a non-Foundation attempt for fun:

let storeVersion = "3.14.10"

let currentVersion = "3.130.10"

func versionToArray(version: String) -> [Int] {
    return split(version) {
        $0 == "."
    }.map {
        // possibly smarter ways to do this
        $0.toInt() ?? 0
    }
}

storeVersion < currentVersion  // false

// true
lexicographicalCompare(versionToArray(storeVersion), versionToArray(currentVersion))
Airspeed Velocity
  • 40,491
  • 8
  • 113
  • 118
1
extension String {

    func compareVersionNumbers(other: String) -> Int {
        
        let nums1 = self.split(separator: ".").map({ Int($0) ?? 0 })
        let nums2 = other.split(separator: ".").map({ Int($0) ?? 0 })
        
        for i in 0..<max(nums1.count, nums2.count) {
            
            let num1 = i < nums1.count ? nums1[i] : 0
            let num2 = i < nums2.count ? nums2[i] : 0
            
            if num1 != num2 {
                return num1 - num2
            }
        }
        
        return 0
    }
}
hasan
  • 23,815
  • 10
  • 63
  • 101
0

Here is a simple swift struct

public struct VersionString: Comparable {

    public let rawValue: String

    public init(_ rawValue: String) {
        self.rawValue = rawValue
    }

    public static func == (lhs: VersionString, rhs: VersionString) -> Bool {
        return lhs.rawValue.compare(rhs.rawValue, options: .numeric) == .orderedSame
    }

    public static func < (lhs: VersionString, rhs: VersionString) -> Bool {
        return lhs.rawValue.compare(rhs.rawValue, options: .numeric) == .orderedAscending
    }
}
0

I can understand there are many good answers given. Here is my version of comparison of app version.

func compareVersions(installVersion: String, storeVersion: String) -> Bool{
        // 1.7.5
        var installedArr = installVersion.components(separatedBy: ".")
        var storeArr = storeVersion.components(separatedBy: ".")
        var isAvailable = false

        while(installedArr.count < storeArr.count){
            installedArr.append("0")
        }

        while(storeArr.count < installedArr.count){
            storeArr.append("0")
        }

        for index in 0 ..< installedArr.count{
            if let storeIndex=storeArr[index].toInt(), let installIndex=installedArr[index].toInt(){
                if storeIndex > installIndex{
                    isAvailable = true
                    return isAvailable
                }
            }
        }

        return isAvailable
    }
Usman Awan
  • 1,208
  • 2
  • 13
  • 30
0

Here is my effort to cover all cases of version formats like compare "10.0" with "10.0.1", let me know if I have missed any case.

Here is the gist https://gist.github.com/shamzahasan88/bc22af2b7c96b6a06a064243a02c8bcc. Hope it helps everyone.

And here is the code if anyone don't want to rate my gist :P

extension String {

  // Version format "12.34.39" where "12" is "Major", "34" is "Minor" and "39" is "Bug fixes"
  // "maximumDigitCountInVersionComponent" is optional parameter determines the maximum length of "Maajor", "Minor" and "Bug Fixes" 
  func shouldUpdateAsCompareToVersion(storeVersion: String, maximumDigitCountInVersionComponent: Int = 5) -> Bool {
        let adjustTralingZeros: (String, Int)->String = { val, count in
            return "\(val)\((0..<(count)).map{_ in "0"}.joined(separator: ""))"
        }
        let adjustLength: ([String.SubSequence], Int)->[String] = { strArray, count in
            return strArray.map {adjustTralingZeros("\($0)", count-$0.count)}
        }
        let storeVersionSubSequence = storeVersion.split(separator: ".")
        let currentVersionSubSequence = self.split(separator: ".")
        let formatter = NumberFormatter()
        formatter.minimumIntegerDigits = maximumDigitCountInVersionComponent
        formatter.maximumIntegerDigits = maximumDigitCountInVersionComponent
        let storeVersions = adjustLength(storeVersionSubSequence, maximumDigitCountInVersionComponent)
        let currentVersions = adjustLength(currentVersionSubSequence, maximumDigitCountInVersionComponent)
        var storeVersionString = storeVersions.joined(separator: "")
        var currentVersionString = currentVersions.joined(separator: "")
        let diff = storeVersionString.count - currentVersionString.count

        if diff > 0 {
            currentVersionString = adjustTralingZeros(currentVersionString, diff)
        }else if diff < 0 {
            storeVersionString = adjustTralingZeros(storeVersionString, -diff)
        }
        let literalStoreVersion = Int(storeVersionString)!
        let literalCurrentVersion = Int(currentVersionString)!
        return literalCurrentVersion < literalStoreVersion
    }
}

Usage:

print("33.29".shouldUpdateAsCompareToVersion(storeVersion: "34.23.19")) // true
print("35.29".shouldUpdateAsCompareToVersion(storeVersion: "34.23.19")) // false
print("34.23.2".shouldUpdateAsCompareToVersion(storeVersion: "34.23.19")) // false
print("34.23.18".shouldUpdateAsCompareToVersion(storeVersion: "34.23.19")) // true
Hamza Hasan
  • 1,368
  • 9
  • 17
0
extension String {
    func versionCompare(_ otherVersion: String) -> ComparisonResult {
        let versionDelimiter = "."

        var versionComponents = self.components(separatedBy: versionDelimiter) // <1>
        var otherVersionComponents = otherVersion.components(separatedBy: versionDelimiter)

        let zeroDiff = versionComponents.count - otherVersionComponents.count // <2>

        if zeroDiff == 0 { // <3>
            // Same format, compare normally
            return self.compare(otherVersion, options: .literal)
        } else {
            let zeros = Array(repeating: "0", count: abs(zeroDiff)) // <4>
            if zeroDiff > 0 {
                otherVersionComponents.append(contentsOf: zeros) // <5>
            } else {
                versionComponents.append(contentsOf: zeros)
            }
            return versionComponents.joined(separator: versionDelimiter)
                .compare(otherVersionComponents.joined(separator: versionDelimiter), options: .literal) // <6>
        }
    }
}

//USAGE

let previousVersionNumber = "1.102.130"
let newAnpStoreVersion = "1.2" // <- Higher

switch previousVersionNumber.versionCompare(newAnpStoreVersion) {
case .orderedAscending:
case .orderedSame:
case .orderedDescending:
}
Kurt L.
  • 623
  • 10
  • 23
0

Async/Await Codable strategy

Once you remove the dots from the version strings, it's a simple Int comparison.

Assumption/Caveat: Version String is always the same number of digits (this fails, if for instance, there is version 1.10.0 since it will be converted to 1110 and version 2.1.0 will be compared as 210). It's not a huge leap to do a comparison of major first, then minor, then patch or incorporate other strategies mentioned in other answers and combine.

// inner json (there are *many* more parameters, but this is the only one we're concerned with)
struct AppUpdateData: Codable {
    let version: String
}
// outer json
struct AppVersionResults: Codable {
    let results: [AppUpdateData]
}

struct AppVersionComparison {
    let currentVersion: Int
    let latestVersion: Int

    var needsUpdate: Bool {
        currentVersion < latestVersion
    }
}
// this isn't really doing much for error handling, could just return an optional
func needsUpdate() async throws -> AppVersionComparison {
    let bundleId = "com.my.bundleId"
    let url = URL(string: "https://itunes.apple.com/lookup?bundleId=\(bundleId)")!
    let (data, _) = try await URLSession.shared.data(from: url)

    let noResultsError: NSError = NSError(domain: #function, code: 999, userInfo: [NSLocalizedDescriptionKey: "No results"])
    do {
        let decoder = JSONDecoder()
        let decoded = try decoder.decode(AppVersionResults.self, from: data)
        guard decoded.results.count > 0 else { throw noResultsError }

        var latestVersionString = decoded.results[0].version
        latestVersionString.removeAll { $0 == "." }

        guard let latestVersion = Int(latestVersionString),
              var currentVersionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
        else { throw noResultsError }

        currentVersionString.removeAll { $0 == "." }
        guard let currentVersion = Int(currentVersionString) else {
            throw noResultsError
        }

        return .init(currentVersion: currentVersion, latestVersion: latestVersion)
    }
}
froggomad
  • 1,747
  • 2
  • 17
  • 40
-1

How about this:

class func compareVersion(_ ver: String, to toVer: String) -> ComparisonResult {
    var ary0 = ver.components(separatedBy: ".").map({ return Int($0) ?? 0 })
    var ary1 = toVer.components(separatedBy: ".").map({ return Int($0) ?? 0 })
    while ary0.count < 3 {
        ary0.append(0)
    }
    while ary1.count < 3 {
        ary1.append(0)
    }
    let des0 = ary0[0...2].description
    let des1 = ary1[0...2].description
    return des0.compare(des1, options: .numeric)
}

and test:

func test_compare_version() {
    XCTAssertEqual(compareVersion("1.0.0", to: "1.0.0"), .orderedSame)
    XCTAssertEqual(compareVersion("1.0.0", to: "1.0."), .orderedSame)
    XCTAssertEqual(compareVersion("1.0.0", to: "1.0"), .orderedSame)
    XCTAssertEqual(compareVersion("1.0.0", to: "1."), .orderedSame)
    XCTAssertEqual(compareVersion("1.0.0", to: "1"), .orderedSame)
    XCTAssertEqual(compareVersion("1.0.0", to: "1.0.1"), .orderedAscending)
    XCTAssertEqual(compareVersion("1.0.0", to: "1.1."), .orderedAscending)
    XCTAssertEqual(compareVersion("1.0.0", to: "1.1"), .orderedAscending)
    XCTAssertEqual(compareVersion("1.0.0", to: "2."), .orderedAscending)
    XCTAssertEqual(compareVersion("1.0.0", to: "2"), .orderedAscending)

    XCTAssertEqual(compareVersion("1.0.0", to: "1.0.0"), .orderedSame)
    XCTAssertEqual(compareVersion("1.0.", to: "1.0.0"), .orderedSame)
    XCTAssertEqual(compareVersion("1.0", to: "1.0.0"), .orderedSame)
    XCTAssertEqual(compareVersion("1.", to: "1.0.0"), .orderedSame)
    XCTAssertEqual(compareVersion("1", to: "1.0.0"), .orderedSame)
    XCTAssertEqual(compareVersion("1.0.1", to: "1.0.0"), .orderedDescending)
    XCTAssertEqual(compareVersion("1.1.", to: "1.0.0"), .orderedDescending)
    XCTAssertEqual(compareVersion("1.1", to: "1.0.0"), .orderedDescending)
    XCTAssertEqual(compareVersion("2.", to: "1.0.0"), .orderedDescending)
    XCTAssertEqual(compareVersion("2", to: "1.0.0"), .orderedDescending)

    XCTAssertEqual(compareVersion("1.0.0", to: "0.9.9"), .orderedDescending)
    XCTAssertEqual(compareVersion("1.0.0", to: "0.9."), .orderedDescending)
    XCTAssertEqual(compareVersion("1.0.0", to: "0.9"), .orderedDescending)
    XCTAssertEqual(compareVersion("1.0.0", to: "0."), .orderedDescending)
    XCTAssertEqual(compareVersion("1.0.0", to: "0"), .orderedDescending)

    XCTAssertEqual(compareVersion("", to: "0"), .orderedSame)
    XCTAssertEqual(compareVersion("0", to: ""), .orderedSame)
    XCTAssertEqual(compareVersion("", to: "1"), .orderedAscending)
    XCTAssertEqual(compareVersion("1", to: ""), .orderedDescending)

    XCTAssertEqual(compareVersion("1.0.0", to: "1.0.0.9"), .orderedSame)
    XCTAssertEqual(compareVersion("1.0.0.9", to: "1.0.0"), .orderedSame)
}
winner.ktw
  • 431
  • 4
  • 12