Validation With Regex Before iOS 16 Using SwiftUI and Combine | by Mark Lucking | Nov, 2022


Validating user input without the new Regex syntax

A slide on Regex variations | code from Apple Developer Docs

You can trace the history of Regex back to an American mathematician, Stephen Cole Kleenewho proposed the idea of ​​text representing patterns using mathematical expressions of sorts in 1951. However, his work did not take off until Ken Thompson decided to use it in his editors QED and ED in 1967, running under UNIX, the forerunning of OSX, iOS, and Android.

Although you could argue that the brevity of Regex expressions almost certainly influenced the syntax of one of the most popular programming languages ​​in the world, namely C, which didn’t come out until 1972. A forerunning to Objective C and now Swift.

Although Regex didn’t find its way into the C language, it got incorporated into dozens of UNIX utilities like grep, awk, and vi — and most modern programming languages. So, it is somewhat surprising [at least for me] that it took almost forty years for Apple to adopt Regex in Objective C, waiting until 2010.

And another decade before, they tried to update it in WWDC2022 by re-writing the syntax to make it more user-friendly. Join me in looking at updating this article and its peer with some more modern Regex. Although I will use the original syntax within the new command framework to a great extent.

Before I start, I want to define 12 rules with Regex syntax that I will match. As I said, the syntax here is almost the same as in 1967, so — well established.

enum Rules:String, CaseIterable 
case alphaRule = "[A-Za-z]+"
case digitRule = "[0-9]+"
case limitedAlphaNumericCombined = "[A-Za-z0-9]4,12"
case limitedAlphaNumericSplit = "[A-Za-z]4,12[0-9]2,4"
case currencyRule = "(\w*)[£$€]+(\w*)"
case wordRule = "(\w+)"
case numericRule = "(\d+)"
case numberFirst = "^(\d+)(\w*)"
case numberLast = "(\w*)(\d+)$"
case spaceRule = "[\S]"
case capitalFirst = "^[A-Z]+[A-Za-z]*"
case punctuationCharacters = "[:punct:]"

In brief, square brackets mean characters in the indicated range; a plus means one or more. The figures in curly brackets are a minimum and maximum number of characters. The double slash followed by a lowercase letter is a character class, and the double slash followed by an uppercase letter is the inverse of said class. An asterisk means zero or more. The hat means must lead with the following character if it is outside the square brackets.

Beware the hat also means the inverse if inside the square brackets. The dollar means must end with said pattern. And finally, you have a POSIX class [:punct:]which is self-explanatory.

With a set of error messages linked [in the same order] to said Regex.

enum Crips:String, CaseIterable 
case alphaRule = "MUST be alpha only"
case digitRule = "MUST be numeric ONLY"
case limitRuleCombined = "MIN 4 AlphaNumeric MAX 12 AlphaNumeric"
case limitRuleSplit = "START MIN 4 Alpha MAX 12 Alpha, FINISH MIN 2 numeric, MAX 4 numeric"
case currencyRule = "MUST contain $£€"
case wordRule = "MUST be alphanumeric"
case numericRule = "MUST be numeric"
case numberFirst = "MUST start with a number"
case numberLast = "MUST finish with a number"
case noSpaces = "MUST not contain spaces or tabs"
case leadingCapital = "MUST start with am uppercase letter"
case punctuationCharacters = "MUST contain punctuation characters"

static var cripList: [String]
return $0.rawValue

But, just before I start, here is an extension I used to help me map the first set of enums to the second. It returns the index of the enum submitted to it.

extension CaseIterable where Self: Equatable 
public func ordinal() -> Self.AllCases.Index
return Self.allCases.firstIndex(of: self)!

Right then — to the main course. I want to keep it as simple as possible. I start by defining a structure within which I will use the messages linked to the regex expressions that don’t match my password string.

struct Diag: Hashable, Codable, Identifiable 
var id = UUID()
var message = ""

A structure that I make Hashable, Codableand Identifiable so that I can use it within a SwiftUI loop. Now, to the main dish, within which I have two main functions. The first here is to match the Regex expressions in my enum.

fileprivate func matchRegex() 
for rule in Rules.allCases
let formulae = try! Regex(rule.rawValue)
if let _ = passText.wholeMatch(of: formulae)
// is good, right size alpha upper + lower & numeric
let diag = Crips.cripList[rule.ordinal()]
diagMsgs.append(Diag(message: "(diag)"))
let foo = /a/

It runs through all the Regex in my sets. Building an array as it does so of diagnostic messages for all the failed matches.

Now, the second function displays a console-like diagnostics list that will scroll off the screen if it gets too big.

fileprivate func displaceFailedMatches() -> ScrollViewReader<ScrollView<VStack<some View>>> {
return ScrollViewReader moveTo in
VStack(alignment: .leading)
ForEach(diagMsgs, id: .id) text in
.font(Fonts.neutonRegular(size: 16))
.onChange(of: diagMsgs) _ in
moveTo.scrollTo(diagMsgs.last?.id, anchor: .bottom)


Finally, I have the main body.

var body: some View {
Spacer(minLength: 4)
TextField("Pass ", text: $passText)
.font(Fonts.neutonRegular(size: 32))

.onChange(of: passText) newValue in
if passText.isEmpty

.padding(.top, 64)



A body that you need to define with the two-state variables shown, referenced in the methods already mentioned.

struct ContentView: View {
@State var passText = ""
@State var diagMsgs:[Diag] = []

Putting it all together, you get a field at the top of the screen where you enter a word. A word was tested against all the array Regexes, reporting the failed matches.

As you see, it is impossible to clear all the error messages since I have conflicting sets. I leave it to the reader to work out which ones they need to suppress to get some working sets.

All of which brings me to the end of this short article. Take a moment to watch the WWDC2022 videos on the subject if you are interested in learning more. Apple has developed a new syntax for Regex expressions, although I caution the reader that maybe they should learn the version that has been around for sixty years or more first [shown in this paper].

It is the flavor of Regex you’re going to find everywhere else.



Please enter your comment!
Please enter your name here