NSString is integer? - objective-c

How to check if the content of a NSString is an integer value? Is there any readily available way?
There got to be some better way then doing something like this:
- (BOOL)isInteger:(NSString *)toCheck {
if([toCheck intValue] != 0) {
return true;
} else if([toCheck isEqualToString:#"0"]) {
return true;
} else {
return false;
}
}

You could use the -intValue or -integerValue methods. Returns zero if the string doesn't start with an integer, which is a bit of a shame as zero is a valid value for an integer.
A better option might be to use [NSScanner scanInt:] which returns a BOOL indicating whether or not it found a suitable value.

Something like this:
NSScanner* scan = [NSScanner scannerWithString:toCheck];
int val;
return [scan scanInt:&val] && [scan isAtEnd];

Building on an answer from #kevbo, this will check for integers >= 0:
if (fooString.length <= 0 || [fooString rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location != NSNotFound) {
NSLog(#"This is not a positive integer");
}
A swift version of the above:
func getPositive(incoming: String) -> String {
if (incoming.characters.count <= 0) || (incoming.rangeOfCharacterFromSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet) != nil) {
return "This is NOT a positive integer"
}
return "YES! +ve integer"
}

Do not forget numbers with decimal point!!!
NSMutableCharacterSet *carSet = [NSMutableCharacterSet characterSetWithCharactersInString:#"0123456789."];
BOOL isNumber = [[subBoldText stringByTrimmingCharactersInSet:carSet] isEqualToString:#""];

func getPositive(input: String) -> String {
if (input.count <= 0) || (input.rangeOfCharacter(from: NSCharacterSet.decimalDigits.inverted) != nil) {
return "This is NOT a positive integer"
}
return "YES! integer"
}
Update #coco's answer for Swift 5

Related

Converting a method from Objective C to Swift

I am starting to learn Swift and hope to find it an excellent replacement for Objective C.
I am attempting to convert my Objective C classes into Swift and I cannot find the best way to translate the following method into Swift.
#implementation VersionReader
- (NSString *)readVersionFromString:(NSString *)string {
if (string.length == 0) {
return nil;
}
unichar firstChar = [string characterAtIndex:0];
if (firstChar < '0' || firstChar > '9') {
return nil;
}
NSUInteger length = string.length;
for (NSUInteger i = 0; i < length; ++i) {
if ([string characterAtIndex:i] == ' ') {
return [string substringToIndex:i];
}
}
return string;
}
#end
So far my Swift code looks like this:
import Cocoa
class VersionReader {
func readVersionFromString(string: String) -> String? {
if (string.isEmpty) {
return nil
}
var firstChar = string.characterAtIndex[0]
if (firstChar < 48 || firstChar > 57) {
return nil
}
var length = string.utf16Count
for (var i = 0; i < length; ++i) {
if (string.characterAtIndex(i) == 32) {
return string.substringToIndex(i)
}
}
return string
}
}
Tom this, I get the same error on two lines:
'String' does not have a member named 'characterAtIndex'
What would be an alternative to make this work in Swift? Thanks in advance.
A possible Swift solution:
func readVersionFromString(string: String) -> String? {
if string.isEmpty {
return nil
}
let firstChar = string[string.startIndex]
if !find("0123456789", firstChar) {
return nil
} else if let pos = find(string, " ") {
return string.substringToIndex(pos)
} else {
return string
}
}
Swift's String type doesn't have a characterAtIndex method. You can cast it to an NSString and use it as below:
var firstChar = (string as NSString).characterAtIndex(0)
Or
var firstChar = string.bridgeToObjectiveC().characterAtIndex(0)
Note that the characterAtIndex method returns an unichar, which seems to be what you want. But the correct approach to get a Swift Character would be that suggested by FreeAsInBeer in his answer: Array(string)[0]
Swift doesn't have a characterAtIndex selector. Instead, you need to use Array(string)[0].

Password validation in UITextField in iOS

I have 1 UITextfield for password in my iPhone application.
I want to validate this textfield with the following validation.
Must be at least 10 characters
Must contain at least one lower case letter, one upper case letter, one digit and one special character
Valid special characters are – ##$%^&+=^.*(?=.{10,})(?=.*d)(?=.*[a-z])(?=.*[A-Z])(?=.*[##$%^&+=]).*$
How can I restrict the UITextField with above requirements?
This is how I would do it. The validation should be done at the end when the user has typed in the password and not in between.I will not be using NSRegularExpression.
-(void)textFieldDidEndEditing:(UITextField *)textField{
int numberofCharacters = 0;
BOOL lowerCaseLetter,upperCaseLetter,digit,specialCharacter = 0;
if([textField.text length] >= 10)
{
for (int i = 0; i < [textfield.text length]; i++)
{
unichar c = [textfield.text characterAtIndex:i];
if(!lowerCaseLetter)
{
lowerCaseLetter = [[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:c];
}
if(!upperCaseLetter)
{
upperCaseLetter = [[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:c];
}
if(!digit)
{
digit = [[NSCharacterSet decimalDigitCharacterSet] characterIsMember:c];
}
if(!specialCharacter)
{
specialCharacter = [[NSCharacterSet symbolCharacterSet] characterIsMember:c];
}
}
if(specialCharacter && digit && lowerCaseLetter && upperCaseLetter)
{
//do what u want
}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Please Ensure that you have at least one lower case letter, one upper case letter, one digit and one special character"
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Please Enter at least 10 password"
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
}
Hope this helps...
You can also do this by using Regex. Here are few example I am providing for you:
// *** Validation for Password ***
// "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$" --> (Minimum 8 characters at least 1 Alphabet and 1 Number)
// "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$#$!%*#?&])[A-Za-z\\d$#$!%*#?&]{8,16}$" --> (Minimum 8 and Maximum 16 characters at least 1 Alphabet, 1 Number and 1 Special Character)
// "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$" --> (Minimum 8 characters at least 1 Uppercase Alphabet, 1 Lowercase Alphabet and 1 Number)
// "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[$#$!%*?&])[A-Za-z\\d$#$!%*?&]{8,}" --> (Minimum 8 characters at least 1 Uppercase Alphabet, 1 Lowercase Alphabet, 1 Number and 1 Special Character)
// "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[$#$!%*?&])[A-Za-z\\d$#$!%*?&]{8,10}" --> (Minimum 8 and Maximum 10 characters at least 1 Uppercase Alphabet, 1 Lowercase Alphabet, 1 Number and 1 Special Character)
Fourth from the list is your case, following code snippet shows how to use it:
-(BOOL)isValidPassword:(NSString *)passwordString
{
NSString *stricterFilterString = #"^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[$#$!%*?&])[A-Za-z\\d$#$!%*?&]{10,}";
NSPredicate *passwordTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", stricterFilterString];
return [passwordTest evaluateWithObject:passwordString];
}
Using the method:
if(![self isValidPassword:txtPassword.text]) {
/* Show alert: "Password must be minimum 10 characters,
at least 1 Uppercase Alphabet, 1 Lowercase Alphabet,
1 Number and 1 Special Character" */
}
else {
// Password is valid
}
Condition: Password should contain atleast 8 characters, 1 uppercase and 1 number
Solution in Swift 3
you can write String Extension like this,
extension String {
func isValidPassword() -> Bool {
let regularExpression = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[$#$!%*?&])[A-Za-z\\d$#$!%*?&]{8,}"
let passwordValidation = NSPredicate.init(format: "SELF MATCHES %#", regularExpression)
return passwordValidation.evaluate(with: self)
}
}
//Example 1
var password = "#Abcdef011" //string from UITextField (Password)
password.isValidPassword() // -> true
//Example 2
var password = "Abcdef011" //string from UITextField
password.isValidPassword() // -> false
or you can write function like this,
func validate(password: String) -> Bool
{
let regularExpression = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[$#$!%*?&])[A-Za-z\\d$#$!%*?&]{8,}"
let passwordValidation = NSPredicate.init(format: "SELF MATCHES %#", regularExpression)
return passwordValidation.evaluate(with: password)
}
this will give you the same result.
Swift 3
check if password is strong ?
length more than or equal 8
lowercase
uppercase
decimal Digits
special characters like !##$%^&*()_-+ is optional
Why i not use regular expression ?
Because it's difficult to support reserved characters in regular
expression syntax.
func isValidated(_ password: String) -> Bool {
var lowerCaseLetter: Bool = false
var upperCaseLetter: Bool = false
var digit: Bool = false
var specialCharacter: Bool = false
if password.characters.count >= 8 {
for char in password.unicodeScalars {
if !lowerCaseLetter {
lowerCaseLetter = CharacterSet.lowercaseLetters.contains(char)
}
if !upperCaseLetter {
upperCaseLetter = CharacterSet.uppercaseLetters.contains(char)
}
if !digit {
digit = CharacterSet.decimalDigits.contains(char)
}
if !specialCharacter {
specialCharacter = CharacterSet.punctuationCharacters.contains(char)
}
}
if specialCharacter || (digit && lowerCaseLetter && upperCaseLetter) {
//do what u want
return true
}
else {
return false
}
}
return false
}
let isVaildPass:Bool = isValidated("Test**00+-")
print(isVaildPass)
You can verify your password validation using the below function just pass a password string and this will return you BOOL value.
-(BOOL) isPasswordValid:(NSString *)pwd {
NSCharacterSet *upperCaseChars = [NSCharacterSet characterSetWithCharactersInString:#"ABCDEFGHIJKLKMNOPQRSTUVWXYZ"];
NSCharacterSet *lowerCaseChars = [NSCharacterSet characterSetWithCharactersInString:#"abcdefghijklmnopqrstuvwxyz"];
//NSCharacterSet *numbers = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"];
if ( [pwd length]<6 || [pwd length]>20 )
return NO; // too long or too short
NSRange rang;
rang = [pwd rangeOfCharacterFromSet:[NSCharacterSet letterCharacterSet]];
if ( !rang.length )
return NO; // no letter
rang = [pwd rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]];
if ( !rang.length )
return NO; // no number;
rang = [pwd rangeOfCharacterFromSet:upperCaseChars];
if ( !rang.length )
return NO; // no uppercase letter;
rang = [pwd rangeOfCharacterFromSet:lowerCaseChars];
if ( !rang.length )
return NO; // no lowerCase Chars;
return YES;
}
for me Best way was to use NSPredicate and regex.
this is regex for your case: ^(?=.{10,})(?=.*[0-9])(?=.*[a-zA-Z])([##$%^&=a-zA-Z0-9_-]+)$
objective C code:
NSString *regex = #"^(?=.{10,})(?=.*[0-9])(?=.*[a-zA-Z])([##$%^&=a-zA-Z0-9_-]+)$";
NSPredicate *passwordTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", regex];
BOOL isValid = [passwordTest evaluateWithObject:yourTextfield.text];
use a regex (NSRegularExpression class has docs on how to write the patten itself) and then :
- (BOOL)textField:(UITextField *)theTextField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
//delete
if (string.length == 0) {
return YES;
}
if (self.regEx) {
NSMutableString* check = [NSMutableString stringWithString:theTextField.text];
[check replaceCharactersInRange:range withString:string];
NSTextCheckingResult* match = [self.regEx firstMatchInString:check options:0 range:NSMakeRange(0, [check length])];
if (match.range.length != check.length) {
return NO;
}
}
}
Warning: Restricting the input this way is really confusing for users. You type and type and the character you type just doesnt appear!
I'd maybe go with a small red (!) next to the test field but I'd always allow the input itself!
I have this elegant solution for Forms (like sign-up) where you have a lot of validation
I have in my custom UITextField the outlet:
#IBInspectable var regexpValidation: String? = nil
In storyboard I can access it through attribute inspector and put regexp string like that (for email):
[a-z0-9!#$%&'*+/=?^_{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?
then in my subclass I have this computed var:
#IBInspectable var regexpValidation: String? = nil // Optional, Set this in InterfaceBuilder
var inputIsValid: Bool {
get {
if let expr = regexpValidation {
return (text.rangeOfString(expr, options: NSStringCompareOptions.RegularExpressionSearch, range: nil, locale: nil) != nil)
} else {
return true
}
}
}
which could be used like this:
override func resignFirstResponder() -> Bool {
if (inputIsValid) {
return super.resignFirstResponder()
}
else {
text = ""
return false
}
}
You need to write your validation code in this delegate method of UITextField
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
Few links that you might want to refer for implementation
how to use regular expression in iOS sdk
iOS TextField Validation
Use the control:isValidObject: method of the NSTextFieldDelegate protocol which allows you to validate the value of a NSTextField. Assuming you have all your interface builder bits and pieces configured correctly, you might do something like this:
#interface PreferencesController : NSWindowController <NSTextFieldDelegate> {
IBOutlet NSTextField *username, *password;
}
#end
#implementation PreferencesController
- (BOOL)control:(NSControl *)control isValidObject:(id)object
{
if (control == password) {
// Perform validation and return YES or NO
}
return YES;
}
#end
SWIFT 5 USING RXSWIFT, a better, neat and reactive approach.
validate password function will be like this, Obviously you can add as many conditions as you want as per your requirement.
func validatePassword(password: String) -> (Bool, String) {
//Minimum 8 characters at least 1 Alphabet and 1 Number:
var tuple: (Bool, String) = (true, "")
var string = "Requires atleast"
if(password.rangeOfCharacter(from: CharacterSet.letters) == nil){
string = "uppercase"
tuple = (false, string)
}
if(password.rangeOfCharacter(from: CharacterSet.decimalDigits) == nil){
string += ", number"
tuple = (false, string)
}
if(password.count < 8 ){
string += ", 8 chars"
tuple = (false, string)
}
return tuple }
func isPasswordValid(in string: String) -> Observable<(Bool, String)> {
return Observable.create { observer -> Disposable in
let tuple = self.validation.validatePasswordForSymbol(password: string)
observer.onNext(tuple)
return Disposables.create()
}
}
You can use aforementioned function in viewModel or VC as per your architecture.
Then invoke the same function like below in your VC.
passwordTextField.rx.text
.orEmpty //1
.filter { $0.count >= 1 } //2
.flatMap { self.isPasswordValid(in: $0) }
.subscribe(onNext: { result in
print("Valid password", result)
//update UI here
// result will be like (false, "Requires atleast, 8 chars, number")
}).disposed(by: disposeBag)

Check if NSNumber is fraction

What is the best way of checking if a NSNumber is a fraction?
NumberIsFraction(#(0)); // NO;
NumberIsFraction(#(0.5)); // YES;
NumberIsFraction(#(1.0)); // NO;
"Best" in terms of border case handling and performance.
Avoiding conversions to types with a smaller domain:
BOOL NumberIsFraction(NSNumber *number) {
double dValue = [number doubleValue];
if (dValue < 0.0)
return (dValue != ceil(dValue));
else
return (dValue != floor(dValue));
}
The solutions from Jonathan, hpique, and prashant all have the same bug: they involve casting NSNumber to fixed-precision types (variously double, long long, etc.), which breaks if the value is a very large NSDecimalNumber. For example, the accepted answer fails on NSDecimalNumber(string: "1000000000000000.1").
A more correct implementation (as an extension, in Swift):
extension NSNumber {
var isFraction: Bool {
var decimal = decimalValue, decimalRounded = decimal
NSDecimalRound(&decimalRounded, &decimal, 0, .down)
return NSDecimalNumber(decimal: decimalRounded) != self
}
}
Or in Objective-C, as a global function:
BOOL NumberIsFraction(NSNumber *number) {
NSDecimal rounded = number.decimalValue;
NSDecimalRound(&rounded, &rounded, 0, NSRoundDown);
return ![number isEqualToNumber:
[[NSDecimalNumber alloc] initWithDecimal:rounded]];
}
I'm currently using:
BOOL NumberIsFraction(NSNumber* number) {
return ![number isEqualToNumber:#(number.longLongValue)];
}
-(BOOL) NumberIsFraction: (NSNumber*)number
{
 NSLog(#"%0.16g",number.doubleValue);
   NSLog(#"%d", number.intValue);
   double diff = number.doubleValue - number.intValue;
   if (diff>0)
return YES;
else return NO;
}

Converting NSString to int [duplicate]

How to check if the content of a NSString is an integer value? Is there any readily available way?
There got to be some better way then doing something like this:
- (BOOL)isInteger:(NSString *)toCheck {
if([toCheck intValue] != 0) {
return true;
} else if([toCheck isEqualToString:#"0"]) {
return true;
} else {
return false;
}
}
You could use the -intValue or -integerValue methods. Returns zero if the string doesn't start with an integer, which is a bit of a shame as zero is a valid value for an integer.
A better option might be to use [NSScanner scanInt:] which returns a BOOL indicating whether or not it found a suitable value.
Something like this:
NSScanner* scan = [NSScanner scannerWithString:toCheck];
int val;
return [scan scanInt:&val] && [scan isAtEnd];
Building on an answer from #kevbo, this will check for integers >= 0:
if (fooString.length <= 0 || [fooString rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location != NSNotFound) {
NSLog(#"This is not a positive integer");
}
A swift version of the above:
func getPositive(incoming: String) -> String {
if (incoming.characters.count <= 0) || (incoming.rangeOfCharacterFromSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet) != nil) {
return "This is NOT a positive integer"
}
return "YES! +ve integer"
}
Do not forget numbers with decimal point!!!
NSMutableCharacterSet *carSet = [NSMutableCharacterSet characterSetWithCharactersInString:#"0123456789."];
BOOL isNumber = [[subBoldText stringByTrimmingCharactersInSet:carSet] isEqualToString:#""];
func getPositive(input: String) -> String {
if (input.count <= 0) || (input.rangeOfCharacter(from: NSCharacterSet.decimalDigits.inverted) != nil) {
return "This is NOT a positive integer"
}
return "YES! integer"
}
Update #coco's answer for Swift 5

Compare version numbers in Objective-C

I am writing an application that receives data with items and version numbers. The numbers are formatted like "1.0.1" or "1.2.5". How can I compare these version numbers? I think they have to be formatted as a string first, no? What options do I have to determine that "1.2.5" comes after "1.0.1"?
This is the simplest way to compare versions, keeping in mind that "1" < "1.0" < "1.0.0":
NSString* requiredVersion = #"1.2.0";
NSString* actualVersion = #"1.1.5";
if ([requiredVersion compare:actualVersion options:NSNumericSearch] == NSOrderedDescending) {
// actualVersion is lower than the requiredVersion
}
I'll add my method, which compares strictly numeric versions (no a, b, RC etc.) with any number of components.
+ (NSComparisonResult)compareVersion:(NSString*)versionOne toVersion:(NSString*)versionTwo {
NSArray* versionOneComp = [versionOne componentsSeparatedByString:#"."];
NSArray* versionTwoComp = [versionTwo componentsSeparatedByString:#"."];
NSInteger pos = 0;
while ([versionOneComp count] > pos || [versionTwoComp count] > pos) {
NSInteger v1 = [versionOneComp count] > pos ? [[versionOneComp objectAtIndex:pos] integerValue] : 0;
NSInteger v2 = [versionTwoComp count] > pos ? [[versionTwoComp objectAtIndex:pos] integerValue] : 0;
if (v1 < v2) {
return NSOrderedAscending;
}
else if (v1 > v2) {
return NSOrderedDescending;
}
pos++;
}
return NSOrderedSame;
}
This is an expansion to Nathan de Vries answer to address the problem of 1 < 1.0 < 1.0.0 etc.
First off we can address the problem of extra ".0"'s on our version string with an NSString category:
#implementation NSString (VersionNumbers)
- (NSString *)shortenedVersionNumberString {
static NSString *const unnecessaryVersionSuffix = #".0";
NSString *shortenedVersionNumber = self;
while ([shortenedVersionNumber hasSuffix:unnecessaryVersionSuffix]) {
shortenedVersionNumber = [shortenedVersionNumber substringToIndex:shortenedVersionNumber.length - unnecessaryVersionSuffix.length];
}
return shortenedVersionNumber;
}
#end
With the above NSString category we can shorten our version numbers to drop the unnecessary .0's
NSString* requiredVersion = #"1.2.0";
NSString* actualVersion = #"1.1.5";
requiredVersion = [requiredVersion shortenedVersionNumberString]; // now 1.2
actualVersion = [actualVersion shortenedVersionNumberString]; // still 1.1.5
Now we can still use the beautifully simple approach proposed by Nathan de Vries:
if ([requiredVersion compare:actualVersion options:NSNumericSearch] == NSOrderedDescending) {
// actualVersion is lower than the requiredVersion
}
I made it myself,use Category..
Source..
#implementation NSString (VersionComparison)
- (NSComparisonResult)compareVersion:(NSString *)version{
NSArray *version1 = [self componentsSeparatedByString:#"."];
NSArray *version2 = [version componentsSeparatedByString:#"."];
for(int i = 0 ; i < version1.count || i < version2.count; i++){
NSInteger value1 = 0;
NSInteger value2 = 0;
if(i < version1.count){
value1 = [version1[i] integerValue];
}
if(i < version2.count){
value2 = [version2[i] integerValue];
}
if(value1 == value2){
continue;
}else{
if(value1 > value2){
return NSOrderedDescending;
}else{
return NSOrderedAscending;
}
}
}
return NSOrderedSame;
}
Test..
NSString *version1 = #"3.3.1";
NSString *version2 = #"3.12.1";
NSComparisonResult result = [version1 compareVersion:version2];
switch (result) {
case NSOrderedAscending:
case NSOrderedDescending:
case NSOrderedSame:
break;
}
Sparkle (the most popular software update framework for MacOS) has a SUStandardVersionComparator class that does this, and also takes into account build numbers and beta markers. I.e. it correctly compares 1.0.5 > 1.0.5b7 or 2.0 (2345) > 2.0 (2100). The code only uses Foundation, so should work fine on iOS as well.
Check out my NSString category that implements easy version checking on github; https://github.com/stijnster/NSString-compareToVersion
[#"1.2.2.4" compareToVersion:#"1.2.2.5"];
This will return a NSComparisonResult which is more accurate then using;
[#"1.2.2" compare:#"1.2.2.5" options:NSNumericSearch]
Helpers are also added;
[#"1.2.2.4" isOlderThanVersion:#"1.2.2.5"];
[#"1.2.2.4" isNewerThanVersion:#"1.2.2.5"];
[#"1.2.2.4" isEqualToVersion:#"1.2.2.5"];
[#"1.2.2.4" isEqualOrOlderThanVersion:#"1.2.2.5"];
[#"1.2.2.4" isEqualOrNewerThanVersion:#"1.2.2.5"];
Swift 2.2 Version :
let currentStoreAppVersion = "1.10.2"
let minimumAppVersionRequired = "1.2.2"
if currentStoreAppVersion.compare(minimumAppVersionRequired, options: NSStringCompareOptions.NumericSearch) ==
NSComparisonResult.OrderedDescending {
print("Current Store version is higher")
} else {
print("Latest New version is higher")
}
Swift 3 Version :
let currentStoreVersion = "1.1.0.2"
let latestMinimumAppVersionRequired = "1.1.1"
if currentStoreVersion.compare(latestMinimumAppVersionRequired, options: NSString.CompareOptions.numeric) == ComparisonResult.orderedDescending {
print("Current version is higher")
} else {
print("Latest version is higher")
}
I thought I'd just share a function I pulled together for this. It is not perfect at all. Please take a look that the examples and results. But if you are checking your own version numbers (which I have to do to manage things like database migrations) then this may help a little.
(also, remove the log statements in the method, of course. those are there to help you see what it does is all)
Tests:
[self isVersion:#"1.0" higherThan:#"0.1"];
[self isVersion:#"1.0" higherThan:#"0.9.5"];
[self isVersion:#"1.0" higherThan:#"0.9.5.1"];
[self isVersion:#"1.0.1" higherThan:#"1.0"];
[self isVersion:#"1.0.0" higherThan:#"1.0.1"];
[self isVersion:#"1.0.0" higherThan:#"1.0.0"];
// alpha tests
[self isVersion:#"1.0b" higherThan:#"1.0a"];
[self isVersion:#"1.0a" higherThan:#"1.0b"];
[self isVersion:#"1.0a" higherThan:#"1.0a"];
[self isVersion:#"1.0" higherThan:#"1.0RC1"];
[self isVersion:#"1.0.1" higherThan:#"1.0RC1"];
Results:
1.0 > 0.1
1.0 > 0.9.5
1.0 > 0.9.5.1
1.0.1 > 1.0
1.0.0 < 1.0.1
1.0.0 == 1.0.0
1.0b > 1.0a
1.0a < 1.0b
1.0a == 1.0a
1.0 < 1.0RC1 <-- FAILURE
1.0.1 < 1.0RC1 <-- FAILURE
notice that alpha works but you have to be very careful with it. once you go alpha at some point you cannot extend that by changing any other minor numbers behind it.
Code:
- (BOOL) isVersion:(NSString *)thisVersionString higherThan:(NSString *)thatVersionString {
// LOWER
if ([thisVersionString compare:thatVersionString options:NSNumericSearch] == NSOrderedAscending) {
NSLog(#"%# < %#", thisVersionString, thatVersionString);
return NO;
}
// EQUAL
if ([thisVersionString compare:thatVersionString options:NSNumericSearch] == NSOrderedSame) {
NSLog(#"%# == %#", thisVersionString, thatVersionString);
return NO;
}
NSLog(#"%# > %#", thisVersionString, thatVersionString);
// HIGHER
return YES;
}
My iOS library AppUpdateTracker contains an NSString category to perform this sort of comparison. (Implementation is based off DonnaLea's answer.)
Usage would be as follows:
[#"1.4" isGreaterThanVersionString:#"1.3"]; // YES
[#"1.4" isLessThanOrEqualToVersionString:#"1.3"]; // NO
Additionally, you can use it to keep track of your app's installation/update status:
[AppUpdateTracker registerForAppUpdatesWithBlock:^(NSString *previousVersion, NSString *currentVersion) {
NSLog(#"app updated from: %# to: %#", previousVersion, currentVersion);
}];
[AppUpdateTracker registerForFirstInstallWithBlock:^(NSTimeInterval installTimeSinceEpoch, NSUInteger installCount) {
NSLog(#"first install detected at: %f amount of times app was (re)installed: %lu", installTimeSinceEpoch, (unsigned long)installCount);
}];
[AppUpdateTracker registerForIncrementedUseCountWithBlock:^(NSUInteger useCount) {
NSLog(#"incremented use count to: %lu", (unsigned long)useCount);
}];
Here is the swift 4.0 + code for version comparison
let currentVersion = "1.2.0"
let oldVersion = "1.1.1"
if currentVersion.compare(oldVersion, options: NSString.CompareOptions.numeric) == ComparisonResult.orderedDescending {
print("Higher")
} else {
print("Lower")
}
Glibc has a function strverscmp and versionsort… unfortunately, not portable to the iPhone, but you can write your own fairly easily. This (untested) re-implementation comes from just reading the documented behavior, and not from reading Glibc's source code.
int strverscmp(const char *s1, const char *s2) {
const char *b1 = s1, *b2 = s2, *e1, *e2;
long n1, n2;
size_t z1, z2;
while (*b1 && *b1 == *b2) b1++, b2++;
if (!*b1 && !*b2) return 0;
e1 = b1, e2 = b2;
while (b1 > s1 && isdigit(b1[-1])) b1--;
while (b2 > s2 && isdigit(b2[-1])) b2--;
n1 = strtol(b1, &e1, 10);
n2 = strtol(b2, &e2, 10);
if (b1 == e1 || b2 == e2) return strcmp(s1, s2);
if (n1 < n2) return -1;
if (n1 > n2) return 1;
z1 = strspn(b1, "0"), z2 = strspn(b2, "0");
if (z1 > z2) return -1;
if (z1 < z2) return 1;
return 0;
}
If you know each version number will have exactly 3 integers separated by dots, you can parse them (e.g. using sscanf(3)) and compare them:
const char *version1str = "1.0.1";
const char *version2str = "1.2.5";
int major1, minor1, patch1;
int major2, minor2, patch2;
if(sscanf(version1str, "%d.%d.%d", &major1, &minor1, &patch1) == 3 &&
sscanf(version2str, "%d.%d.%d", &major2, &minor2, &patch2) == 3)
{
// Parsing succeeded, now compare the integers
if(major1 > major2 ||
(major1 == major2 && (minor1 > minor2 ||
(minor1 == minor2 && patch1 > patch2))))
{
// version1 > version2
}
else if(major1 == major2 && minor1 == minor2 && patch1 == patch2)
{
// version1 == version2
}
else
{
// version1 < version2
}
}
else
{
// Handle error, parsing failed
}
To check the version in swift you can use following
switch newVersion.compare(currentversion, options: NSStringCompareOptions.NumericSearch) {
case .OrderedDescending:
println("NewVersion available ")
// Show Alert Here
case .OrderedAscending:
println("NewVersion Not available ")
default:
println("default")
}
Hope it might be helpful.
Here is a recursive function that do the works with multiple version formatting of any length. It also works for #"1.0" and #"1.0.0"
static inline NSComparisonResult versioncmp(const NSString * a, const NSString * b)
{
if ([a isEqualToString:#""] && [b isEqualToString:#""]) {
return NSOrderedSame;
}
if ([a isEqualToString:#""]) {
a = #"0";
}
if ([b isEqualToString:#""]) {
b = #"0";
}
NSArray<NSString*> * aComponents = [a componentsSeparatedByString:#"."];
NSArray<NSString*> * bComponents = [b componentsSeparatedByString:#"."];
NSComparisonResult r = [aComponents[0] compare:bComponents[0] options:NSNumericSearch];
if(r != NSOrderedSame) {
return r;
} else {
NSString* newA = (a.length == aComponents[0].length) ? #"" : [a substringFromIndex:aComponents[0].length+1];
NSString* newB = (b.length == bComponents[0].length) ? #"" : [b substringFromIndex:bComponents[0].length+1];
return versioncmp(newA, newB);
}
}
Test samples :
versioncmp(#"11.5", #"8.2.3");
versioncmp(#"1.5", #"8.2.3");
versioncmp(#"1.0", #"1.0.0");
versioncmp(#"11.5.3.4.1.2", #"11.5.3.4.1.2");
Based on #nathan-de-vries 's answer, I wrote SemanticVersion.swift for comparing Semantic Version, and here is the test cases.