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 中文版
如何在 SwiftUI App 中開發 Live Activities
SwiftUI 框架

如何在 SwiftUI App 中開發 Live Activities

Live Activities 首次於 iOS 16 推出,是 Apple 最令人興奮的更新之一,能讓 App 與使用者在即時互動上更有連結。它不再需要使用者不斷打開 App,Live Activities 可以讓資訊直接顯示在鎖定畫面和 Dynamic Island 上。
使用 Tool Calling 強化 Foundation Models 功能
AI

使用 Tool Calling 強化 Foundation Models 功能

在前幾篇教學中,我們介紹了 Foundation Models 在 iOS 26 中的運作方式,以及如何使用這個全新框架打造具備 AI 功能的應用。我們也介紹了 @Generable 巨集,它能輕鬆地將模型回應轉換為結構化的 Swift 類型。 現在,在這個 Foundation
活用 Foundation Models 的 @Generable 與 @Guide 製作測驗 App
AI

活用 Foundation Models 的 @Generable 與 @Guide 製作測驗 App

在前一篇教學中,我們介紹了 Foundation Models 框架,並示範了如何用它來進行基本的內容生成。那個過程相當簡單——你提供一個提示詞(prompt),等幾秒鐘,就能獲得自然語言的回應。在我們的範例中,我們建立了一個簡單的問答 App,讓使用者可以提問,App 則直接顯示生成的文字。 但如果回應變得更複雜——你需要把非結構化文字轉換為結構化的物件呢? 舉例來說,
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。