Swift validation allow all alphabets letters but no special characters or numbers

Viewed 2069

I have a TextField and I want users to type only letters, English, German, French, Cyrillic etc... alphabets, but no numbers or special characters(emojis). Spaces are allowed. How can I achieve this?

TextField("First Name", text: $firstName, onEditingChanged: { change in
    // validate
})

I have seen a lot of similar questions but they take care only of English alphabets.

extension String {
    var containsLetters : Bool {
        let lettersAndSpaces = CharacterSet.letters.union(CharacterSet.whitespaces)
        return self.rangeOfCharacter(from: lettersAndSpaces) != nil
    }
}
2 Answers

The short answer, as you an Filip have found, is CharacterSet.letters, which represents all Unicode code points that are considered by Unicode to be "letters" or "marks."

extension String {
    func containsOnlyLettersAndWhitespace() -> Bool {
        let allowed = CharacterSet.letters.union(.whitespaces)
        return unicodeScalars.allSatisfy(allowed.contains)
    }
}

And that will probably do what you want. It covers every letter of every alphabet in Unicode. But it also includes lots of things that aren't alphabets, such as Chinese characters. Your "etc..." doesn't make it clear if you meant to include those, but I suspect you do.

Since it includes all marks, it luckily will handle é correctly in both composed and decomposed forms. But it also will consider x́ a letter, even though I'm pretty certain it's not a letter in any language. But again, probably what you mean, so that's ok.

Of course that means it supports Zalgo, which you probably don't mean, but again is probably ok(?):

"Z̤͑̈́͢ͅâ̭ͭ͝l͔̑̾̐͏͓͓̯̝ͧ̿͋go̧̗͙̳͗ͩ͐͘͜".containsOnlyLettersAndWhitespace()   // true

It also supports some illegal things like combining marks without characters:

let acute = "\u{0301}"
acute.containsOnlyLettersAndWhitespace()

I'm guessing that's ok.

On the other hand, some letter-like things are not letters, like letters in a circle:

"Ⓐ".containsOnlyLettersAndWhitespace()  // false

And you probably mean that, so that's good.

But there are also a few things that might surprise. For example:

"ǃ".containsOnlyLettersAndWhitespace()  // true

That's not an exclamation point. That's a letter in ǃKung. So again, that's probably what you want. But just warning you that sometimes people use letters for things other than their intended purpose.

" ͡ ͜ʖ ͡".containsOnlyLettersAndWhitespace() // true
"op ǝldoǝd sƃuᴉɥʇ ǝɥʇ".containsOnlyLettersAndWhitespace()  // true

That "etc..." opens up a lot of interesting corners.


Since this is about names, that complicates things significantly. I would first very carefully consider what you mean by "real name." My father's name is Robert William Napier II. He's universally known as Bill. Is "Bill" a real name or a fake name? What consistent rule decides that? He's had trouble his whole life with systems that reject "II" as a suffix.

"Only letters and whitespace" breaks a lot of names in English. Many common names include periods, hyphens, and apostrophes, and for full names, commas.

Excluding numbers is a bit dicey as well. "3rd" is a legitimate part of a "real" name in English. One of the vice chairs of the Emoji Subcommittee is named Jennifer 8. Lee. The press release spells it "Jennifer 8 Lee" without the period, but she generally writes it with a period. It is very unusual to put a period after a number this way, but the rule is quite simple: People are always correct about the spelling and pronunciation of their own name.

If the goal is to reject emoji, then reject emoji. At least today it is probably valid to say no "real name" (whatever that actually means) includes an emoji.

extension String {
    /**
     Looks for characters whose default presentation style is emoji, and for
     VARIATION SELECTOR-16, which switches to emoji presentation
     */
    func containsNoEmoji() -> Bool {
        let vs16: Unicode.Scalar = "\u{fe0f}"
        return unicodeScalars.allSatisfy {
            !$0.properties.isEmojiPresentation && $0 != vs16
        }
    }
}

Note that this will reject , which is an emoji, but not ❤, which is a dingbat.

If you want to be stricter about names and reject things like Zalgo, text faces, and "inverted" text, then you're going to have to be precise about what cultures you support, research how those cultures spell names, and implement specific rules. For example, to avoid Zalgo, you might restrict to 1 combining character for English-speaking cultures, which is probably sufficient, and may be sufficient for most Western Latin-using cultures, but Vietnamese (also a Latin-using language) requires at least two combining marks when written in NFD. I don't know any Latin-using culture that needs more than that, but I would not bet against it. You have to research.

To avoid text faces and inverted text, I would probably again decide what cultures you support, and restrict characters to those character sets (which is pretty straightforward to implement). But this means explicitly choosing not to support other cultures. That's the choice. That said, some popular characters like COMBINING DOUBLE INVERTED BREVE ( ͡   ) aren't part of any modern language I'm aware of. So you could exclude a few of those and probably avoid most things you're probably trying to avoid.

My recommendation is to be as accepting as you possibly can of people's names, and only write exclusions for things that cause actual problems, and then write them as narrowly as possible.

Related