You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI TextField输入时添加千分位逗号并限制合法字符

SwiftUI TextField 同时实现合法字符限制与千分位格式化

我开发了一个通用SwiftUI应用,需要实现两个核心功能:

  • TextField仅允许输入数字、点(.)和逗号(,)
  • 用户输入时自动以千分位逗号格式化内容(例如250000显示为250,000)

已实现的单独功能

1. 合法字符输入限制

通过绑定字符串类型的TextField,结合onReceive过滤非法字符:

TextField(placeholder, text: $text)
    .padding(.horizontal, 12)
    .font(.arialDisplay(.regular, size: 14))
    .foregroundColor(isDarkMode ? .white : .black)
    .onChange(of: text) { _ in
        // 输入长度相关逻辑
    }
    .onReceive(Just(text)) { newValue in
        let allowedCharacters = "0123456789.,"
        let filtered = newValue.filter { allowedCharacters.contains($0) }
        if filtered != newValue {
            self.text = filtered
        }
    }

2. 千分位格式化

使用绑定Double类型的TextField,利用系统内置格式化器实现自动千分位:

@State private var value = 0.0

TextField("Value", value: $value, format: .number
    .grouping(.automatic)
    .precision(.fractionLength(2))
)

遇到的问题

同时应用上述两个功能时,输入限制逻辑失效。由于需要适配iPad,无法通过设置键盘类型(如.numberPad)来限制输入。尝试过将两者结合的方案,但仅格式化生效,输入限制不工作:

import SwiftUI
import Combine

extension String {
    func toDouble() -> Double? {
        return NumberFormatter().number(from: self)?.doubleValue
    }
}

struct Solution2: View {
    @State private var text: Double = 0

    var body: some View {
        TextField("", value: $text, format: .currency(code: Locale.current.currency?.identifier ?? "USD"))
            .onChange(of: text) { newValue in }
            .onReceive(Just(text)) { newValue in
                let newString: String = String(format: "%f", newValue)
                let allowedCharacters = "0123456789.$,"
                let filtered = newString.filter { allowedCharacters.contains($0) }
                if filtered != newString {
                    self.text = filtered.toDouble() ?? 0
                }
            }
    }
}

可行解决方案

核心思路是分离输入模型与显示文本:用Double类型变量存储实际数值,用String类型变量处理输入过滤,同时保持两者同步并应用规范格式化。

完整实现代码

import SwiftUI
import Combine

struct NumberTextField: View {
    @Binding var value: Double
    let placeholder: String
    let isDarkMode: Bool
    
    // 处理用户原始输入的临时字符串
    @State private var inputText: String = ""
    // 自定义格式化器,控制千分位和小数位数
    private let numberFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.groupingSeparator = ","
        formatter.decimalSeparator = "."
        formatter.maximumFractionDigits = 2
        formatter.minimumFractionDigits = 0
        return formatter
    }()
    
    var body: some View {
        TextField(placeholder, text: $inputText)
            .padding(.horizontal, 12)
            .font(.arialDisplay(.regular, size: 14))
            .foregroundColor(isDarkMode ? .white : .black)
            .onChange(of: inputText) { newValue in
                filterAndFormatInput(newValue)
            }
            .onAppear {
                // 初始化时将已有数值转为格式化后的文本
                inputText = numberFormatter.string(from: NSNumber(value: value)) ?? ""
            }
    }
    
    private func filterAndFormatInput(_ rawInput: String) {
        // 1. 过滤非法字符,仅保留数字、点、逗号
        let allowedCharacters = CharacterSet(charactersIn: "0123456789.,")
        let filteredString = rawInput.components(separatedBy: allowedCharacters.inverted).joined()
        
        // 2. 清理格式错误:确保只有一个小数点,移除手动输入的逗号(格式化器会自动添加规范千分位)
        var processedString = filteredString
        let dotCount = processedString.filter { $0 == "." }.count
        if dotCount > 1 {
            let parts = processedString.split(separator: ".", maxSplits: 1)
            processedString = "\(parts[0]).\(parts[1...].joined())"
        }
        processedString = processedString.replacingOccurrences(of: ",", with: "")
        
        // 3. 同步数值与格式化文本
        if let numericValue = Double(processedString) {
            value = numericValue
            inputText = numberFormatter.string(from: NSNumber(value: numericValue)) ?? processedString
        } else {
            inputText = processedString
            value = 0
        }
    }
}

// 使用示例
struct ContentView: View {
    @State private var amount: Double = 250000
    @Environment(\.colorScheme) private var colorScheme
    
    var body: some View {
        NumberTextField(
            value: $amount,
            placeholder: "请输入金额",
            isDarkMode: colorScheme == .dark
        )
    }
}

方案说明

  1. 输入与数据分离:inputText处理用户输入的原始字符串,value存储业务逻辑需要的纯数值,避免格式化字符干扰数值计算。
  2. 分层处理输入:先过滤非法字符,再清理格式错误,最后通过格式化器生成规范的千分位文本,保证输入和显示的一致性。
  3. 适配性强:不依赖特定键盘类型,完美适配iPad等设备,同时支持深色模式切换。

内容的提问来源于stack exchange,提问作者iosdev1111

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 15:37:04