Swift 5 - Error with the password validation regex - ios

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/

Related

Use RegEx to validate a password on iOS Swift

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"

How to get range of specific substring even if a duplicate

I want to detect the words that begin with a #, and return their specific ranges. Initially I tried using the following code:
for word in words {
if word.hasPrefix("#") {
let matchRange = theSentence.range(of: word)
//Do stuff with this word
}
}
This works fine, except if you have a duplicate hashtag it will return the range of the first occurrence of the hashtag. This is because of the nature of the range(_:) function.
Say I have the following string:
"The range of #hashtag should be different to this #hashtag"
This will return (13, 8) for both hashtags, when really it should return (13, 8) as well as (50, 8). How can this be fixed? Please note that emojis should be able to be detected in the hashtag too.
EDIT
If you want to know how to do this with emojis to, go here
Create regex for that and use it with the NSRegularExpression and find the matches range.
var str = "The range of #hashtag should be different to this #hashtag"
let regex = try NSRegularExpression(pattern: "(#[A-Za-z0-9]*)", options: [])
let matches = regex.matchesInString(str, options:[], range:NSMakeRange(0, str.characters.count))
for match in matches {
print("match = \(match.range)")
}
Why don't you separate your word in chunks where each chunk starts with #. Then you can know how many times your word with # appears in sentence.
Edit: I think that regex answer is the best way for this but this is an other approach for same solution.
var hastagWords = [""]
for word in words {
if word.hasPrefix("#") {
// Collect all words which begin with # in an array
hastagWords.append(word)
}
}
// Create a copy of original word since we will change it
var mutatedWord = word.copy() as! String
for hashtagWord in hastagWords {
let range = mutatedWord.range(of: hashtagWord)
if let aRange = range {
// If range is OK then remove the word from original word and go to an other range
mutatedWord = mutatedWord.replacingCharacters(in: aRange, with: "")
}
}

Parsing & contracting Russian full names

I have several text fields used to enter full name and short name, among other data. My task is:
Check if entered full name matches the standard Russian Cyrillic full name pattern:
Иванов Иван Иванович (three capitalized Cyrillic strings separated by spaces)
If it matches, create another string by auto-contracting full name according to pattern below and enter it to the corresponding text field:
Иванов И.И. (first string, space, first character of the second string, dot, first character of the third string, dot)
If it doesn't match, do nothing.
Currently I use the following code:
let fullNameArray = fullNameField.text!.characters.split{$0 == " "}.map(String.init)
if fullNameArray.count == 3 {
if fullNameArray[0] == fullNameArray[0].capitalizedString && fullNameArray[1] == fullNameArray[1].capitalizedString && fullNameArray[2] == fullNameArray[2].capitalizedString {
shortNameField.text = "\(fullNameArray[0]) \(fullNameArray[1].characters.first!).\(fullNameArray[2].characters.first!)."
}
}
How can I improve it? Maybe regular expressions could help me? If so, could you post some sample code?
My current solution:
do {
let regex = try NSRegularExpression(pattern: "^\\p{Lu}\\p{Ll}+\\s\\p{Lu}\\p{Ll}+\\s\\p{Lu}\\p{Ll}+$", options: .AnchorsMatchLines)
if regex.firstMatchInString(fullNameField.text!, options: [], range: NSMakeRange(0, fullNameField.text!.characters.count)) != nil {
let fullNameArray = fullNameField.text!.characters.split(" ").map(String.init)
shortNameField.text = "\(fullNameArray[0]) \(fullNameArray[1].characters.first!).\(fullNameArray[2].characters.first!)."
}
else {
shortNameField.text = ""
}
} catch let error as NSError {
print(error.localizedDescription)
}
Processes my full name pattern correctly.

iOS - regex to match word boundary, including underscore

I have a regex that I'm trying to run to match a variety of search terms. For example:
the search "old" should match:
-> age_old
-> old_age
but not
-> bold - as it's not at the start of the word
To do this, I was using a word boundary. However, word boundary doesn't take into account underscores. As mentioned here, there are work arounds available in other languages. Unfortunately, with NSRegularExpression, this doesn't look possible. Is there any other way to get a word boundary to work? Or other options?
TLDR: Use one of the following:
let rx = "(?<=_|\\b)old(?=_|\\b)"
let rx = "(?<![^\\W_])old(?![^\\W_])"
let rx = "(?<![\\p{L}\\d])old(?![\\p{L}\\d])"
See a regex demo #1, regex demo #2 and regex demo #3.
Swift and Objective C support ICU regex flavor. This flavor supports look-behinds of fixed and constrained width.
(?= ... )    Look-ahead assertion. True if the parenthesized pattern matches at the current input position, but does not advance the input position.
(?! ... )    Negative look-ahead assertion. True if the parenthesized pattern does not match at the current input position. Does not advance the input position.
(?<= ... )    Look-behind assertion. True if the parenthesized pattern matches text preceding the current input position, with the last character of the match being the input character just before the current position. Does not alter the input position. The length of possible strings matched by the look-behind pattern must not be unbounded (no * or + operators.)
(?<! ... )    Negative Look-behind assertion.
So, you can use
let regex = "(?<![\\p{L}\\d])old(?![\\p{L}\\d])";
See regex demo
Here is a Swift code snippet extracting all "old"s:
func matchesForRegexInText(regex: String, text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: [])
let nsString = text as NSString
let results = regex.matchesInString(text,
options: [], range: NSMakeRange(0, nsString.length))
return results.map { nsString.substringWithRange($0.range)}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
let s = "age_old -> old_age but not -> bold"
let rx = "(?<![\\p{L}\\d])old(?![\\p{L}\\d])"
let matches = matchesForRegexInText(rx, text: s)
print(matches) // => ["old", "old"]

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