Use RegEx to validate a password on iOS Swift - ios

I need to validate a user's password against the following requirements:
8 or more characters.
Contains 1 character and 1 number
Can enter letters, numbers, and symbols
Does anyone know how I can accomplish this using a RegEx?
I've made attempts to solve this problem on my own, but nothing I've tried so far as worked. The code for my latest attempt is below.
func isPasswordHasEightCharacter(password: String) -> Bool {
let passWordRegEx = "^.{8,}$"
let passwordTest = NSPredicate(format: "SELF MATCHES %#", passWordRegEx)
return passwordTest.evaluate(with: password)
}
func isPasswordHasNumberAndCharacter(password: String) -> Bool {
let passRegEx = "^(?=.*[a-z])(?=.*[0-9])"
let passwordTest = NSPredicate(format: "SELF MATCHES %#", passRegEx)
return passwordTest.evaluate(with: password)
}
func isPasswordHasNumberAndCharacterSign(password: String) -> Bool {
let passWordRegEx = "^(?!.[^a-zA-Z0-9##${'$'}^+=])"
let passwordTest = NSPredicate(format: "SELF MATCHES %#", passWordRegEx)
return passwordTest.evaluate(with: password)
}

In this solution each requirement is checked individually to avoid complex regular expressions. This solution supports variants of characters like ôöệż etc
func validatePassword(_ password: String) -> Bool {
//At least 8 characters
if password.count < 8 {
return false
}
//At least one digit
if password.range(of: #"\d+"#, options: .regularExpression) == nil {
return false
}
//At least one letter
if password.range(of: #"\p{Alphabetic}+"#, options: .regularExpression) == nil {
return false
}
//No whitespace charcters
if password.range(of: #"\s+"#, options: .regularExpression) != nil {
return false
}
return true
}
Some test cases
print(validatePassword("abc")) // --> false
print(validatePassword("abcdefgh")) // --> false
print(validatePassword("abcde fgh1")) // --> false
print(validatePassword("abcdefgh1")) // --> true
print(validatePassword("abcåäö123")) // --> true
print(validatePassword("ABC123€%&")) // --> true
print(validatePassword("#èệżôøö123")) // --> true

The main issue is that NSPredicate with MATCHES requires the full string to match and consume the whole input. Lookarounds - you are using lookaheads - do not consume text, that is, the texts they match are not added to the match value and the regex index stays where it was before attempting to match a lookaround pattern.
The last two parts can be thus fixed this way:
func isPasswordHasNumberAndCharacter(password: String) -> Bool {
let passRegEx = "(?=[^a-z]*[a-z])[^0-9]*[0-9].*"
let passwordTest = NSPredicate(format: "SELF MATCHES %#", passRegEx)
return passwordTest.evaluate(with: password)
}
func isPasswordHasNumberAndCharacterSign(password: String) -> Bool {
let passWordRegEx = "[a-zA-Z0-9!##$%^&*]+"
let passwordTest = NSPredicate(format: "SELF MATCHES %#", passWordRegEx)
return passwordTest.evaluate(with: password)
}
The first part is OK, though you do not need the ^ and $ anchors (as the whole string input must match the pattern). However, to check a string length you do not even need a regex: see Get the length of a String.
Note:
^(?=[^a-z]*[a-z])[^0-9]*[0-9].*\z matches a string that contains at least one lowercase ASCII letter and at least one ASCII digit
^[a-zA-Z0-9!##$%^&*]+$ will match a string that only contains ASCII letters, digits and some specific symbols. If you inlude a - make sure it is at the end. Escape both [ and ] if you need to add them, too.
If you want to combine all that into 1 regex you could use
let passRegEx = "(?=[^a-z]*[a-z])(?=[^0-9]*[0-9])[a-zA-Z0-9!##$%^&*]{8,}"
Or, if you are not using the regex with the NSPredicate MATCHES, with anchors:
let passRegEx = "\\A(?=[^a-z]*[a-z])(?=[^0-9]*[0-9])[a-zA-Z0-9!##$%^&*]{8,}\\z"

Related

Swift 5 - Error with the password validation regex

I am making a sign-in/up function on Swift. I am trying to validate a password using regex. The passwords have the following requirements
At least 7 characters long
At least one uppercase letter
At least one number
This is the validation regex I was using: "^(?=.[A-Z])(?=.[0-9]){7}$"
And this is my code
let passwordTest = NSPredicate(format: "SELF MATCHES %#", "^(?=.[A-Z])(?=.[0-9]){7}$")
Whenever I try to log in with a password that meets these requirements (eg. Greatpass13), Xcode gives me an error saying
Thread 1: "Can't do regex matching, reason: (Can't open pattern U_REGEX_RULE_SYNTAX (string Greatpass13, pattern ^(?=.[A-Z])(?=.[0-9]){7}$, case 0, canon 0))"
(?=^.{7,}$)(?=^.*[A-Z].*$)(?=^.*\d.*$).*
Short Explanation
(?=^.{7,}$) At least 7 characters long
(?=^.*[A-Z].*$) At least one uppercase letter
(?=^.*\d.*$) At least one number
.* Match the string that contains all assertions
See the regex demo
Swift Example
let phonePattern = #"(?=^.{7,}$)(?=^.*[A-Z].*$)(?=^.*\d.*$).*"#
func isValid(password: String) -> Bool {
return password.range(
of: phonePattern,
options: .regularExpression
) != nil
}
print(isValid(password: "Pass1")) // false
print(isValid(password: "Pass23word")) // true
print(isValid(password: "23password")) // false
print(isValid(password: "Greatpass13")) // true
you forgot to add dot before counter 7
func isValidPassword() -> Bool {
let password = self.trimmingCharacters(in: CharacterSet.whitespaces)
let passwordRegx = "^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{7}$"
let passwordCheck = NSPredicate(format: "SELF MATCHES %#",passwordRegx)
return passwordCheck.evaluate(with: password)
}
at least one uppercase,
at least one digit
at least one lowercase
min 7 characters total
Here is the updated regex based on yours.
let passwordRegx = "^(?=.*?[A-Z])(?=.*?[0-9]).{7,}$"
let passwordPredicate = NSPredicate(format: "SELF MATCHES %#",passwordRegx)
Also you need to add {7,} at the end otherwise it will only match for 7 characters and not more than that.
You can test your regex at:
https://regex101.com/

Validate URL without scheme

Swift 5, Xcode 10, iOS 12
My code uses UIApplication.shared.canOpenURL to validate URLs, which unfortunately fails without e.g. "http://".
Example:
print(UIApplication.shared.canOpenURL(URL(string: "stackoverflow.com")!)) //false
print(UIApplication.shared.canOpenURL(URL(string: "http://stackoverflow.com")!)) //true
print(UIApplication.shared.canOpenURL(URL(string: "129.0.0.1")!)) //false
print(UIApplication.shared.canOpenURL(URL(string: "ftp://129.0.0.1")!)) //true
I'm aware of the change with schemes (iOS9+) and I know that I can just add a prefix like "http://" if the String doesn't start with it already, then check this new String but I'm still wondering:
Question: How do I add a "there's no scheme" scheme, so valid URLs like "stackoverflow.com" return true too (is this even possible?)?
It's not possible to add a valid scheme to URL because no one knows which prefix will be add to which URL. You can just validate a URL with the help of regex.
I searched and modified the regex.
extension String {
func isValidUrl() -> Bool {
let regex = "((http|https|ftp)://)?((\\w)*|([0-9]*)|([-|_])*)+([\\.|/]((\\w)*|([0-9]*)|([-|_])*))+"
let predicate = NSPredicate(format: "SELF MATCHES %#", regex)
return predicate.evaluate(with: self)
}
}
I tested it with below urls:
print("http://stackoverflow.com".isValidUrl())
print("stackoverflow.com".isValidUrl())
print("ftp://127.0.0.1".isValidUrl())
print("www.google.com".isValidUrl())
print("127.0.0.1".isValidUrl())
print("127".isValidUrl())
print("hello".isValidUrl())
Output
true
true
true
true
true
false
false
Note: 100% regex is not possible to validate the email and url
This is the method that I use
extension String {
/// Return first available URL in the string else nil
func checkForURL() -> NSRange? {
guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
return nil
}
let matches = detector.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count))
for match in matches {
guard Range(match.range, in: self) != nil else { continue }
return match.range
}
return nil
}
func getURLIfPresent() -> String? {
guard let range = self.checkForURL() else{
return nil
}
guard let stringRange = Range(range,in:self) else {
return nil
}
return String(self[stringRange])
}
}
Apparently, the method name and the comment in the code are not verbose enough, so here is the explanation.
Used NSDataDetector and provided it the type - NSTextCheckingResult.CheckingType.link to check for links.
This goes through the string provided and returns all the matches for URL type.
This checks for link in the string that you provide, if any, else returns nil.
The method getURLIfPresent return the URL part from that string.
Here are a few examples
print("http://stackoverflow.com".getURLIfPresent())
print("stackoverflow.com".getURLIfPresent())
print("ftp://127.0.0.1".getURLIfPresent())
print("www.google.com".getURLIfPresent())
print("127.0.0.1".getURLIfPresent())
print("127".getURLIfPresent())
print("hello".getURLIfPresent())
Output
Optional("http://stackoverflow.com")
Optional("stackoverflow.com")
Optional("ftp://127.0.0.1")
Optional("www.google.com")
nil
nil
nil
But, this doesn't return true for "127.0.0.1". So I don't think it will fulfil your cause.
In your case, going the regex way is better it seems. As you can add more conditions if you come across some more patterns that demand to be considered as URL.

backspace not work in outside of regex in swift

I use this method for patterning the phone number in UITextField at the .editingChange event
But the delete key only removes the numbers
extension String{
func applyPatternOnNumbers(pattern: String) -> String {
let replacmentCharacter: Character = "#"
let pureNumber = self.replacingOccurrences( of: "[^۰-۹0-9]", with: "", options: .regularExpression)
var result = ""
var pureNumberIndex = pureNumber.startIndex
for patternCharacter in pattern {
if patternCharacter == replacmentCharacter {
guard pureNumberIndex < pureNumber.endIndex else { return result }
result.append(pureNumber[pureNumberIndex])
pureNumber.formIndex(after: &pureNumberIndex)
} else {
result.append(patternCharacter)
}
}
return result
}
}
use at the editingChange event
let pattern = "+# (###) ###-####"
let mobile = textField.text.substring(to: pattern.count-1)
textfield.text = mobile.applyPatternOnNumbers(pattern: pattern)
// print(textfield.text) +1 (800) 666-8888
the problem is space & - , ( , ) chars can not to be removed
The RegEx you are trying is to not consider digits only:
[^۰-۹0-9]
I'm not sure, but you may change it to:
[^۰-۹0-9\s-\(\)]
and it may work. You might just add a \ before your special chars inside [] and you can any other chars into it that you do not need to be replaced.
Or you may simplify it to
[^\d\s-\(\)]
and it might work.
Method 2
You may use this RegEx which is an exact match to the phone number format you are having:
\+\d+\s\(\d{3}\)\s\d{3}-\d{4}
You may remove the first +, if it is unnecessary
\d+\s\(\d{3}\)\s\d{3}-\d{4}

Swift RegEx International

I am checking for a valid birthday in a regEx after the user has entered their birthday in a date picker.
The issue is that if the phone is in Russian, Chinese, or any other language that uses other characters, the RegEx will not work due the month.
Is there an easy way to convert the month to digits (1-12) even though the month is displayed as letters and also work in any language?
If there was such a way, easy or not, it would be a lot easier to check if the user is exactly 13 years or older instead of just using the year.
func isValidBirthday(testStr4:String) -> Bool {
println("validate birthday: \(testStr4)")
let birthdayRegEx = "[A-Z0-9a-z, .]+(19[0-9][0-9]|200[0-3]|2003)"
let birthdayTest = NSPredicate(format: "SELF MATCHES %#", birthdayRegEx)
return birthdayTest.evaluateWithObject(testStr4)
}
I have a utilities class for checking Regex, using in a lot of my project
class Regex {
let internalExpression: NSRegularExpression!
let pattern: String
init(_ pattern: String) {
self.pattern = pattern
var error: NSError?
internalExpression = NSRegularExpression(pattern: pattern, options: .CaseInsensitive, error: &error)
}
func test(input: String) -> Bool {
if let matches = internalExpression?.matchesInString(input, options: nil, range:NSMakeRange(0, count(input))) {
return matches.count > 0
}
return false
}
}
For your case:
if Regex("^(19|20)\\d\\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$").test(DateStringValue) {
// Code to be executed in here
}
Hope it help.

Check if string is 3 chars and 3 number in Swift

I'm trying to create a function that validate my string if it is using this format
ABC123
First three characters should be letters and the other 3 should be numbers
I have no idea on how to start
Thanks
You can do it with a regular expression match on strings, like this:
let str = "ABC123"
let optRange = str.rangeOfString("^[A-Za-z]{3}\\d{3}$", options: .RegularExpressionSearch)
if let range = optRange {
println("Matched")
} else {
println("Not matched")
}
Regex above requires that the match occupied the whole string (the ^ and $ anchors at both ends), has three letters [A-Za-z]{3} and three digits \\d{3}.
You can also use it as an extension if you would like to:
extension String {
var match: Bool {
return rangeOfString("^[A-Za-z]{3}\\d{3}$", options: .RegularExpressionSearch) != nil
}
}
"ABC123".match // true

Resources