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

SwiftUI中TextField格式化输入后如何维持光标位置?

在SwiftUI中实时格式化TextField输入并维持光标位置

可以仅通过SwiftUI(支持iOS 16+)实现实时格式化输入并保持正确的光标位置。核心思路是跟踪用户输入中的有效数字位置,在格式化后将光标映射到对应数字的下一个位置,自动跳过括号、空格、连字符这类格式化符号。

修正后的实现代码

替换原ContentView的实现,以下是完整可运行版本:

import SwiftUI

struct ContentView: View {
    @State var phoneNumber = PhoneNumber(areaCode: "617", exchange: "555", number: "1212")
    @State var phoneNumberString = ""
    @State private var selection: TextSelection?
    
    // 计算原始输入中光标位置对应的有效数字索引
    private func getDigitPosition(in input: String, cursorIndex: String.Index) -> Int {
        let substring = input[..<cursorIndex]
        return substring.filter { $0.isWholeNumber }.count
    }
    
    // 将有效数字索引映射到格式化字符串中的光标位置
    private func mapCursorPosition(formattedString: String, digitPosition: Int) -> String.Index {
        var currentDigitCount = 0
        var currentIndex = formattedString.startIndex
        
        while currentIndex < formattedString.endIndex {
            let char = formattedString[currentIndex]
            if char.isWholeNumber || char == "_" {
                if currentDigitCount == digitPosition {
                    return currentIndex
                }
                currentDigitCount += 1
            }
            formattedString.formIndex(after: &currentIndex)
        }
        return formattedString.endIndex
    }
    
    var body: some View {
        VStack {
            TextField(value: $phoneNumber, format: .phoneNumber) {
                Text("内置格式化(提交后生效)")
            }
            
            TextField("实时格式化输入", text: $phoneNumberString, selection: $selection)
                .onChange(of: phoneNumberString) { oldValue, newValue in
                    guard let parsedNumber = try? PhoneNumber.PhoneNumberFormatStyle().parseStrategy.parse(newValue) else {
                        return
                    }
                    
                    let formatted = parsedNumber.formatted(.phoneNumber)
                    guard formatted != newValue else { return }
                    
                    // 记录当前光标对应的有效数字位置
                    let cursorPosition: Int
                    if let selection = selection {
                        switch selection.indices {
                        case .selection(let range):
                            cursorPosition = getDigitPosition(in: newValue, cursorIndex: range.upperBound)
                        case .insertion(let index):
                            cursorPosition = getDigitPosition(in: newValue, cursorIndex: index)
                        }
                    } else {
                        cursorPosition = getDigitPosition(in: newValue, cursorIndex: newValue.endIndex)
                    }
                    
                    // 更新格式化后的文本
                    phoneNumberString = formatted
                    
                    // 映射并恢复光标位置
                    DispatchQueue.main.async {
                        let newCursorIndex = mapCursorPosition(formattedString: formatted, digitPosition: cursorPosition)
                        selection = .init(insertionPoint: newCursorIndex)
                    }
                }
        }
        .textFieldStyle(RoundedBorderTextFieldStyle())
        .padding()
    }
}

关键逻辑说明

  1. 有效数字位置计算:getDigitPosition函数统计光标之前的所有有效数字数量,忽略格式化符号,只关注用户实际输入的数字位置,而非字符位置。
  2. 光标位置映射:mapCursorPosition函数遍历格式化后的字符串,找到对应数字位置的索引,自动跳过括号、空格、连字符,确保光标停留在可输入数字的位置。
  3. 异步更新光标:修改文本后通过DispatchQueue.main.async延迟设置光标,确保SwiftUI完成文本更新后再调整光标位置,避免状态冲突。

兼容性说明

该方案支持iOS 16及以上版本,依赖TextField的selection绑定API(iOS 16引入)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 12:04:52