47

In Swift, how can I check if a String is alphanumeric, ie, if it contains only one or more alphanumeric characters [a-zA-Z0-9], excluding letters with diacritics, eg, é.

ma11hew28
  • 121,420
  • 116
  • 450
  • 651
  • In Objective-C: http://stackoverflow.com/questions/1671605/how-to-check-if-a-string-only-contains-alphanumeric-characters-in-objective-c – ma11hew28 Mar 14 '16 at 16:23

8 Answers8

81
extension String {
    var isAlphanumeric: Bool {
        return !isEmpty && range(of: "[^a-zA-Z0-9]", options: .regularExpression) == nil
    }
}

"".isAlphanumeric        // false
"abc".isAlphanumeric     // true
"123".isAlphanumeric     // true
"ABC123".isAlphanumeric  // true
"iOS 9".isAlphanumeric   // false
ma11hew28
  • 121,420
  • 116
  • 450
  • 651
  • Why not make it a computed property instead? – Tim Vermeulen Mar 14 '16 at 17:02
  • 4
    You might want to use this regex, it's intended for this use: `^[:alnum:]+$` –  Mar 14 '16 at 19:16
  • @TimVermeulen That'd work too. I guess I went with a method because it does quite a bit more checking than say `Set.isEmpty`. Also, [in Python, `str.isalnum()` is a method](https://docs.python.org/library/stdtypes.html#str.isalnum). – ma11hew28 Mar 14 '16 at 21:49
  • @KennethBruno I tried that, but in iOS 9.2, `[:alnum:]` includes letters with diacritics, e.g., é, which is not what I want. I just reported this issue to Apple. – ma11hew28 Mar 14 '16 at 23:29
  • Yes, it does. If you want to exclude letters outside the ASCII character set use: `^([:ascii:]|[:alnum])+$`. The benefit of this over the one in the answer is that your intent is clearer, it's plain you want only ASCII and alphanumeric characters. –  Mar 15 '16 at 00:10
  • @MattDiPasquale I don't really agree with your reasoning, whether a string is alphanumeric or not is a property, no matter how it was computed. – Tim Vermeulen Mar 15 '16 at 12:44
  • 1
    @TimVermeulen well, another reason I initially made it a method is that Swift has methods like `UnicodeDecodingResult.isEmptyInput()` & `UnicodeScalar.isASCII()`. But, I just noticed that Swift also has properties like `String.isEmpty`, `StaticString.isASCII`, and `Float.isNaN`. So, I changed it to a property. I also made a couple Swift bug reports: https://bugs.swift.org/browse/SR-950 & https://bugs.swift.org/browse/SR-951. – ma11hew28 Mar 15 '16 at 18:47
  • Yeah, `UIKit` still isn't very consistent regarding properties and functions (among other things). Thanks for reporting, I voted for them. – Tim Vermeulen Mar 15 '16 at 18:52
  • @TimVermeulen sure. Thanks! :-) And, thank you for your feedback here. – ma11hew28 Mar 15 '16 at 19:16
  • @KennethBruno thank you, but `[a-zA-Z0-9]` is shorter & (to me) more explicit. I don't always remember (and so have to look up) which characters the `[:ascii:]` & `[:alnum:]` POSIX character classes include—Not to mention, acronyms (like ASCII) & abbreviations (like alnum) tend to sacrifice clarity. Also, I thought `(a|b)` meant `a` or `b`, so it's unclear to me that `([:ascii:]|[:alnum])` means `[:ascii:]` *and* `[:alnum]`. – ma11hew28 Mar 15 '16 at 19:32
  • The nature of the code is that we want to include anything that is `not (ascii or alphanumeric)`, translating that with [De Morgan's laws](https://en.wikipedia.org/wiki/De_Morgan%27s_laws) we get including anything that is `(not ascii) and (not alphanumeric)`. So you can express it with either `and` or `or`, it just takes knowing the algorithms deeply enough. Of course, use whatever works best for you! –  Mar 15 '16 at 21:05
  • 1
    Non-english words with è, for example, will break this. – gavanon Oct 21 '17 at 00:34
  • @gavanon, yes, that's what I want. – ma11hew28 Oct 21 '17 at 14:30
  • "abcd".isAlphanumeric giving me // true. But it's not alphanumeric. – Soumen May 23 '18 at 09:54
  • @Soumen I implemented this property to work like [Python's isalnum](https://docs.python.org/3/library/stdtypes.html#str.isalnum) method, which returns true if the string isn't empty and all of its characters are alphanumeric. – ma11hew28 Jun 18 '18 at 14:10
30

A modern Swift 3 and 4 solution

extension String {

    func isAlphanumeric() -> Bool {
        return self.rangeOfCharacter(from: CharacterSet.alphanumerics.inverted) == nil && self != ""
    }

    func isAlphanumeric(ignoreDiacritics: Bool = false) -> Bool {
        if ignoreDiacritics {
            return self.range(of: "[^a-zA-Z0-9]", options: .regularExpression) == nil && self != ""
        }
        else {
            return self.isAlphanumeric()
        }
    }

}

Usage:

"".isAlphanumeric()         == false
"Hello".isAlphanumeric()    == true
"Hello 2".isAlphanumeric()  == false
"Hello3".isAlphanumeric()   == true

"Français".isAlphanumeric() == true
"Français".isAlphanumeric(ignoreDiacritics: true) == false

This works with languages other than English, allowing diacritic characters like è and á, etc. If you'd like to ignore these, use the flag "ignoreDiacritics: true".

gavanon
  • 1,293
  • 14
  • 15
  • 1
    This should be the accepted answer. It's cleaner and I suspect it will perform better than any RegEx expression. – R OMS Oct 21 '17 at 14:18
  • But, this solution allows letters with diacritics, eg, é, which is not what I want. – ma11hew28 Oct 21 '17 at 14:32
  • @ma11hew28 you should consider allowing for diacritics. Input can unexpectedly have them if user input is anywhere in your code. They are alpha numeric. – gavanon Oct 21 '17 at 14:37
  • Thank you, @gavanon, I will consider it. But, I am validating usernames for a platform, and I want the users to be able to use the their usernames for their accounts on other platforms, such as Facebook & Instagram, neither of which allow usernames to contain letters with diacritics. – ma11hew28 Oct 21 '17 at 14:49
  • @ma11hew28 That's to avoid people creating fake accounts like "Mícrosoft" (See what I did there?). I see you updated your question stressing the ignoring of diacritics, so I've included the ability to ignore them in my edited answer. :) – gavanon Oct 21 '17 at 17:08
  • Nice. Thank you. :-) I think you can delete the first function from your answer and just use its body in the second function. No, sorry, I don't see what you did there. ¯\\_(ツ)_/¯ – ma11hew28 Oct 22 '17 at 04:14
  • I’ll leave both just for those who want the extra feature. As for Mícrosoft, what I did was use a special í with a diacritic. You didn’t even notice - hence why they’re not allowed in Twitter user names. ;) – gavanon Oct 22 '17 at 14:52
  • "Hello".isAlphanumeric() == true how this can be true? – Soumen May 23 '18 at 08:49
20

I felt the accepted answer using regex was concrete but a rather heavy solution. You could check it this way also in Swift 3.0:

if yourString.rangeOfCharacter(from: CharacterSet.alphanumerics.inverted) != nil {
    return "Username can only contain numbers or digits"
}
carbonr
  • 6,049
  • 5
  • 46
  • 73
7
extension String {
    /// Allows only `a-zA-Z0-9`
    public var isAlphanumeric: Bool {
        guard !isEmpty else {
            return false
        }
        let allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
        let characterSet = CharacterSet(charactersIn: allowed)
        guard rangeOfCharacter(from: characterSet.inverted) == nil else {
            return false
        }
        return true
    }
}

XCTAssertFalse("".isAlphanumeric)
XCTAssertFalse("climate change".isAlphanumeric)
XCTAssertFalse("Poüet".isAlphanumeric)
XCTAssertTrue("Hawking2018".isAlphanumeric)
neoneye
  • 50,398
  • 25
  • 166
  • 151
  • Checked now and find it's working. I was checking in the playground may be made some mistake then. Thanks. – Soumen May 23 '18 at 12:12
3

The problem with the CharacterSet.alphanumerics CharacterSet is that it is more permissive than [a-zA-Z0-9]. It contains letters with diacritics, Eastern Arabic numerals, etc.

assert(["e", "E", "3"].allSatisfy({ CharacterSet.alphanumerics.contains($0) }))
assert(["ê", "É", "٣"].allSatisfy({ CharacterSet.alphanumerics.contains($0) }))

You can build your own CharacterSet using only the specific 62 "alphanumeric" characters:

extension CharacterSet {
    
    static var alphanumeric62: CharacterSet {
        return lowercase26.union(uppercase26).union(digits10)
    }
    
    static var lowercase26: CharacterSet { CharacterSet(charactersIn: "a"..."z") }
    static var uppercase26: CharacterSet { CharacterSet(charactersIn: "A"..."Z") }
    static var digits10:    CharacterSet { CharacterSet(charactersIn: "0"..."9") }
    
}

assert(["e", "E", "3"].allSatisfy({ CharacterSet.alphanumeric62.contains($0) }))
assert(["ê", "É", "٣"].allSatisfy({ CharacterSet.alphanumeric62.contains($0) == false }))

Then test your string against the inverse of that CharacterSet:

guard "string".rangeOfCharacter(from: CharacterSet.alphanumeric62.inverted) == nil else {
    fatalError()
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
2

Here's a succinct approach:

extension String {
    var isAlphanumeric: Bool {
       allSatisfy { $0.isLetter || $0.isNumber }
    }
}
rmp251
  • 5,018
  • 4
  • 34
  • 46
  • 1
    Problem with this approach is it will allow diacritical variants, such as `é`. A workaround is to add `$0.isASCII` to the check. – Ranoiaetep May 12 '22 at 05:33
1

This regex used to check that string contains atleast 1 alphabet + atleast 1 digit alongwith 8 characters.

"^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$"
Uma Madhavi
  • 4,851
  • 5
  • 38
  • 73
1
extension String {  
   var isAlphaNumeric: Bool {
        let hasLetters = rangeOfCharacter(from: .letters, options: .numeric, range: nil) != nil
        let hasNumbers = rangeOfCharacter(from: .decimalDigits, options: .literal, range: nil) != nil
        let comps = components(separatedBy: .alphanumerics)
        return comps.joined(separator: "").count == 0 && hasLetters && hasNumbers  
   } 
}