Using Regex
Regex is one approach, but if using it, we may combine your specifications into a single regex search, making use of the positive lookahead assertion technique from the following Q&A:
Here, using the regex:
^(?=.*[A-Za-z])(?=.*[0-9])(?!.*[^A-Za-z0-9]).{5,15}$
// where:
// (?=.*[A-Za-z]) Ensures string has at least one letter.
// (?=.*[0-9]) Ensures string has at least one digit.
// (?!.*[^A-Za-z0-9]) Ensures string has no invalid (non-letter/-digit) chars.
// .{5,15} Ensures length of string is in span 5...15.
Where I've include also a negative lookahead assertion (?!...
) to invalidate the password given any invalid characters.
We may implement the regex search as follows:
extension String {
func isValidPassword() -> Bool {
let regexInclude = try! NSRegularExpression(pattern: "^(?=.*[A-Za-z])(?=.*[0-9])(?!.*[^A-Za-z0-9]).{5,15}$")
return regexInclude.firstMatch(in: self, options: [], range: NSRange(location: 0, length: characters.count)) != nil
}
}
let pw1 = "hs1bés2" // invalid character
let pw2 = "12345678" // no letters
let pw3 = "shrt" // short
let pw4 = "A12345" // ok
print(pw1.isValidPassword()) // false
print(pw2.isValidPassword()) // false
print(pw3.isValidPassword()) // false
print(pw4.isValidPassword()) // true
Using Set
/ CharacterSet
A Swift native approach is using sets of explicitly specified Character
's:
extension String {
private static var numbersSet = Set("1234567890".characters)
private static var alphabetSet = Set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLKMNOPQRSTUVWXYZ".characters)
func isValidPassword() -> Bool {
return 5...15 ~= characters.count &&
characters.contains(where: String.numbersSet.contains) &&
characters.contains(where: String.alphabetSet.contains)
}
}
Or, similarly, using the Foundation
method rangeOfCharacter(from:)
over CharacterSet
's:
extension String {
private static var numbersSet = CharacterSet(charactersIn: "1234567890")
private static var alphabetSet = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLKMNOPQRSTUVWXYZ")
func isValidPassword() -> Bool {
return 5...15 ~= characters.count &&
rangeOfCharacter(from: String.numbersSet) != nil &&
rangeOfCharacter(from: String.alphabetSet) != nil
}
}
If you'd also like to reject passwords that contain any character that is not in the specified sets, you could add a search operation on the (inverted) union of your sets (possibly you also allow some special characters that you'd like to include in this union). E.g., for the CharacterSet
example:
extension String {
private static var numbersSet = CharacterSet(charactersIn: "1234567890")
private static var alphabetSet = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLKMNOPQRSTUVWXYZ")
func isValidPassword() -> Bool {
return 5...15 ~= characters.count &&
rangeOfCharacter(from: String.numbersSet.union(String.alphabetSet).inverted) == nil &&
rangeOfCharacter(from: String.numbersSet) != nil &&
rangeOfCharacter(from: String.alphabetSet) != nil
}
}
let pw1 = "hs1bés2" // invalid character
let pw2 = "12345678" // no letter
let pw3 = "shrt" // too short
let pw4 = "A12345" // OK
print(pw1.isValidPassword()) // false
print(pw2.isValidPassword()) // false
print(pw3.isValidPassword()) // false
print(pw4.isValidPassword()) // true
Using pattern matching
Just for the discussion of it, yet another approach is using native Swift pattern matching:
extension String {
private static var numberPattern = Character("0")..."9"
private static var alphabetPattern = Character("a")..."z"
func isValidPassword() -> Bool {
return 5...15 ~= characters.count &&
characters.contains { String.numberPattern ~= $0 } &&
lowercased().characters.contains { String.alphabetPattern ~= $0 }
}
}
let pw1 = "hs1bs2"
let pw2 = "12345678"
let pw3 = "shrt"
let pw4 = "A12345"
print(pw1.isValidPassword()) // true
print(pw2.isValidPassword()) // false
print(pw3.isValidPassword()) // false
print(pw4.isValidPassword()) // true
Just note that this approach will allow letters with diacritics (and similar) to pass as the minimum 1 letter specification, e.g.:
let diacritic: Character = "é"
print(Character("a")..."z" ~= diacritic) // true
let pw5 = "12345é6"
print(pw5.isValidPassword()) // true
as these are contained the the Character
range "a"..."z"
; see e.g. the excellent answer in the following thread: