Comparing Version Strings

How do you compare two version strings in Swift? For example, how can I check if version “2.2.5” is higher than “2.0.3” or that a version is at least “1.8.5”? As long as your version strings follow a consistent format it turns out to be not too hard.

Version Strings

Version strings can take many formats. They may not follow strict semantic versioning but they typically have at least some recognizable numerical format:

v2.5
1.0.3
4.9a

Parsing the string ourselves is a pain. Luckily the Foundation framework has a method for comparing the order of two strings:

compare(_:options:range:locale:)

We can omit the range and locale parameters. They default to comparing the whole string and the system locale. To compare two numeric strings we need to specify .numeric for the options. So to compare two versions strings:

let version1 = "1.5"
let version2 = "2.0"
version1.compare(version2, options: .numeric)

The return value is a ComparisonResult. This is an example of a closed, or frozen, enum with three possible values:

  • orderedSame
  • orderedAscending
  • orderedDescending

For brevity, let’s use a method to wrap the arguments:

func compareNumeric(_ version1: String, _ version2: String) -> ComparisonResult {
  return version1.compare(version2, options: .numeric)
}

Some examples of usage:

compareNumeric("1.0","2.0")        // ascending
compareNumeric("v2.9","v3.0")      // ascending
compareNumeric("1.1.5", "2.0.0")   // ascending
compareNumeric("2.1", "2.0")       // descending
compareNumeric("2.0.0", "1.0.1")   // descending
compareNumeric("2.4", "2.4")       // same
compareNumeric("0.0", "0.1a")      // ascending
compareNumeric("0.1c", "0.1b")     // descending
compareNumeric("2019.4", "2018.5") // descending

Keep It Simple

One caveat, you need to be sure your version strings have a consistent format. For example, comparing two versions with a different format can give surprising results:

compareNumeric("2.0", "2.0.0")     // ascending

This can also cause problems with pre-release versioning:

compareNumeric("1.0.0-rc.1", "1.0.0")  // descending
compareNumeric("1.0.0-alpha", "1.0.0") // descending

You’ll need to parse or normalize the strings yourself if you expect those types of versions.