Joe's Blog

iOS 開發筆記

SwiftUI TextField 限制輸入小數

發佈於 2026-06-13

UIKit 版本靠 shouldChangeCharactersIn 攔截每一次按鍵,SwiftUI 沒有這個 delegate,思路需要換一下:改成在 onChange 裡拿到變更後的完整字串,驗證、修正,再寫回去。

UIKit 版本:UITextField 限制輸入小數


實作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct DecimalTextField: View {
    @Binding var text: String

    var body: some View {
        TextField("0.00", text: $text)
            .keyboardType(.decimalPad)
            .onChange(of: text) { _, newValue in
                text = filtered(newValue)
            }
    }
}

private extension DecimalTextField {
    func filtered(_ input: String) -> String {
        // 只保留數字和小數點
        let cleaned = input.filter { "0123456789.".contains($0) }

        // 總長度限制
        guard cleaned.count <= 10 else { return text }

        // 只允許一個小數點
        let parts = cleaned.components(separatedBy: ".")
        guard parts.count <= 2 else { return text }

        // 小數點後最多 2 位
        if parts.count == 2, parts[1].count > 2 { return text }

        // 首位是 "." 自動補成 "0."
        if cleaned.hasPrefix(".") { return "0" + cleaned }

        // 首位是 0 且下一位不是 ".",移除多餘的 0
        if cleaned.count >= 2, cleaned.hasPrefix("0"), !cleaned.hasPrefix("0.") {
            return String(cleaned.dropFirst())
        }

        return cleaned
    }
}

與 UIKit 版的差異

UIKitSwiftUI
攔截時機按鍵當下(字元尚未寫入)變更後(拿到完整新字串)
復原方式return false 阻止把舊值寫回 text
自動補值直接改 textField.text把新值寫回 text
程式碼位置delegate 方法onChange + private func

SwiftUI 的 onChange 是在字串已經改變後才觸發,所以不能「阻止」輸入,只能「修正」回合法值。這是兩個版本最根本的差異。


使用方式

1
2
3
4
5
6
7
8
struct ContentView: View {
    @State private var amount = ""

    var body: some View {
        DecimalTextField(text: $amount)
            .padding()
    }
}

本文使用 Claude 共同完成