SwiftUI 框架

Swift 5.7:應用新的 Regex 語法 在 SwiftUI 和 Combine 驗證使用者的輸入

Regex 歷史悠久,在許多 UNIX 工具中獲廣泛應用。Apple 在 WWDC2022 重寫了 Regex 語法,讓使用者用起來更方便。在這篇文章中,Mark 會帶大家用更現代的 Regex 語法,建立一個框架來驗證使用者設定的密碼!
Swift 5.7:應用新的 Regex 語法 在 SwiftUI 和 Combine 驗證使用者的輸入
Photo by Nangialai Stoman on Unsplash
In: SwiftUI 框架, swift 5.7
本篇原文(標題:Validation With Regex in Swift 5.7 Using SwiftUI and Combine)刊登於作者 Medium,由 Mark Lucking 所著,並授權翻譯及轉載。

有關正規表示式(Regular expression,regex)的歷史,我們可以追溯到 1951 年,美國數學家 Stephen Cole Kleene 提出了一個想法,就是使用數學符號來表達字串規律。直到 1967 年,Ken Thompson 把這個想法套用於他的編輯器 QED 和 UNIX 系統 (OSX、iOS 和 Android 的前身)下的 ED,這個想法就開始獲廣泛應用。

可能有人會覺得簡潔的 Regex 深深影響到 C 語言的語法。C 語言在 1972 年推出,可以說是世界上最廣泛應用的編程語言,Objective C 和現在的 Swift 的先驅。

雖然 Regex 沒有被納入 C 語言,但它在許多 UNIX 工具中獲廣泛應用,像是 grep、awk、vi、和大部分現代編程語言。因此,我很意外 Apple 竟然過了接近四十年 ── 直到 2010 年 ── 才在 Objective C 中採用 Regex。

而又過了 10 年之後,Apple 在 WWDC2022 重寫了 Regex 語法,讓使用者用起來更方便。在這篇文章中,我會帶大家用更現代的 Regex 更新 Validate Passwords in SwiftUI Forms Using CombineData Validation in SwiftUI 2.0 這兩篇文章。不過,我會盡量使用新命令框架 (command framework) 內的原始語法。

範例程式碼

在開始之前,讓我們先定義本篇文章會用到的 12 條 Regex 語法規則。如我剛才所說,以下的語法規則自 1967 年已經開始推行,因此可以說是十分完善。

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:]"
}

簡單來說,方括號([])就表示所指示範圍內的字符,加號(+)表示出現一次或以上。大括號({})中的數字是最小和最大字符數。雙反斜線(\\)後加上一個小寫字母是一個字符類別,而雙反斜線後加上一個大寫字母該類別的相反。星號(*)表示出現零次或以上。如果在方括號外有插入符號(^),就表示必須以其字符開頭。

而插入符號在方括號內,也表示相反的意思。金錢符號($)就表示必須以其字符為結尾。最後,我們會有一個 POSIX 類別 [:punct:]

以下是對應上文 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 an uppercase letter"
    case punctuationCharacters = "MUST contain punctuation characters"
    
    static var cripList: [String] {
        return Crips.allCases.map { $0.rawValue }
    }
}

在開始之前,我們還需要一個 extension,來幫助我們把第一組 enum map 到第二組,然後它會回傳我們提交的 enum 的 index。

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

我們可以進入正題了,這篇教學。讓我們先定義一個結構,在結構中我們會使用密碼字符不符合 Regex 規則相應的錯誤訊息。

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

這個結構是 HashableCodable、和 Identifiable 的,因此我們可以在 SwiftUI loop 內使用它。在結構裡面,我們有兩個主要函式,第一個就是在 enum 內匹配 Regex。

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
            } else {
                let diag = Crips.cripList[rule.ordinal()]
                diagMsgs.append(Diag(message: "\(diag)"))
                let foo = /\a/
                print(foo)
            }
        }
    }

它會檢查所有 Regex 是否能夠符合所有規則,並為相應的錯誤訊息構建一個陣列 (array)。

而第二個函數則顯示一個類似控制台的診斷清單 (diagnostics list),這個清單如果太長,會變成一個滾動視圖。

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

最後,Main body 應該是這樣的:

var body: some View {
        VStack {
            HStack {
                Spacer(minLength: 4)
                TextField("Pass ", text: $passText)
                    .font(Fonts.neutonRegular(size: 32))
                    
                    .onChange(of: passText) { newValue in
                        diagMsgs.removeAll()
                        matchRegex()
                        if passText.isEmpty {
                            diagMsgs.removeAll()
                        }
                    }
                    .border(Color.black)
                    .padding(.top, 64)
                Spacer()
            }
            displaceFailedMatches()
        }
    }

我們需要使用以下的 two-state 變數定義主體,而其 reference 的方法在前文已經提過了。

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

把所有程式碼放在一起,螢幕頂部會出現一個 field,讓我們可以輸入字詞。讓我們試試輸入一個字詞,下面就會顯示所有無法符合的規則。

如你所見,由於規則有衝突,我們根本無法清除所有錯誤訊息。大家可以想想需要刪除哪些規則,才可以建立出可行的範例。

這篇教學文章到此為止。如果你有興趣了解更多,可以花點時間看看相關的 WWDC2022 影片。雖然 Apple 已經為 Regex 開發了一種新的語法,不過我建議大家可以先了解已經存在了 60 年以上的版本(如這篇教學文章所示)。

以上就是隨處可見的 Regex 語法的簡介。

本篇原文(標題:Validation With Regex in Swift 5.7 Using SwiftUI and Combine)刊登於作者 Medium,由 Mark Lucking 所著,並授權翻譯及轉載。
作者簡介:Mark Lucking,編程資歷超過 35 年,熱愛使用及學習 Swift/iOS 開發,定期在 Better ProgrammingThe StartUpMac O’ClockLevel Up Coding、及其它平台上發表文章。
譯者簡介:Kelly Chan-AppCoda 編輯小姐。
作者
AppCoda 編輯團隊
此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。
評論
更多來自 AppCoda 中文版
如何使用 Swift 整合 Google Gemini AI
SwiftUI 框架

如何使用 Swift 整合 Google Gemini AI

在即將到來的 WWDC,Apple 預計將會發佈一個本地端的大型語言模型 (LLM)。 接下來的 iOS SDK 版本將讓開發者更輕易地整合 AI 功能至他們的應用程式中。然而,當我們正在等待 Apple 推出自家的生成 AI 模型時,其他公司(如 OpenAI
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。