How to compare two app version strings in Swift

⋅ 4 min read ⋅ Swift String

Table of Contents

Comparing an app version is an easy task for a human. You can tell right away which version is newer, e.g., 2.1.0 is higher than 1.0.0 and 2.0.0, but it might not easy when you try to do this in code. You might try to do it manually with the help of string splitting. What you might not aware of is, if you version strings have a consistent format, you can compare it with a standard String method built right in the Foundation framework.

Version Strings

In this post, I will focus on iOS version number.

From CFBundleShortVersionString

The release version number is a string composed of three period-separated integers. The first integer represents major revision to the app, such as a revision that implements new features or major changes. The second integer denotes a revision that implements less prominent features. The third integer represents a maintenance release revision.

The following are some valid examples:

1.0
1.0.3
2.0.0

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Comparing

To compare app version strings, you can use compare(_:options:range:) with an option of .numeric.

let version1 = "1.0.0"
let version2 = "2.0.0"
version1.compare(version2, options: .numeric)
// .orderedAscending

For brevity, I will create a String extension to wrap compare(_:options:range:) and call it versionCompare(_:).

extension String {
func versionCompare(_ otherVersion: String) -> ComparisonResult {
return self.compare(otherVersion, options: .numeric)
}
}

Some examples of usage:

"1.0.0".versionCompare("1.0.0") // .orderedSame
"0.0.2".versionCompare("0.0.1") // .orderedDescending
"0.1".versionCompare("0.0.1") // .orderedDescending
"0.1.0".versionCompare("0.0.1") // .orderedDescending
"0.2".versionCompare("0.1") // .orderedDescending
"1.0.0".versionCompare("1.1") // .orderedAscending
"0.1.0".versionCompare("1.0.0") // .orderedAscending
"0.0.1".versionCompare("1.0.0") // .orderedAscending
"0.0.1".versionCompare("1") // .orderedAscending
"1.0".versionCompare("2") // .orderedAscending

Caveat

There is one problem with this compare(_:options:range:) method. It will give a wrong comparison result if a version is the same but in a different format.

"1.0.0".versionCompare("1")     // .orderedDescending
"1.0.0".versionCompare("1.0") // .orderedDescending
"1.0.0".versionCompare("1.0.") // .orderedDescending

Fix

To prevent this, make sure your version strings have a consistent format (same number of period and zeros). You can do extra work to make sure your version strings have the same period and zeros. The following is my adjustment to versionCompare(_:) to do just that.

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: .numeric)
} 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: .numeric) // <6>
}
}

<1> We split the version by period (.).
<2> Then, we find the difference of digit that we will zero pad.
<3> If there are no differences, we don't need to do anything and use simple .compare.
<4> We populate an array of missing zero.
<5> We add zero pad array to a version with a fewer period and zero.
<6> We user array components to build back our versions from components and compare them. This time it will have the same period and number of digit.

This method will make sure we two version strings got an identical period and the same number of digit before making the comparison.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Read more article about Swift, String, or see all available topic

Enjoy the read?

If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Buy me a coffee Tweet Share
Previous
How to split a string into an array of substrings in Swift

Learn different ways to split a string into an array of substrings.

Next
Different ways to check for String prefix in Swift

Learn how to get a prefix from a Swift string.

← Home