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: ¤tIndex) } 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() } }
关键逻辑说明
- 有效数字位置计算:
getDigitPosition函数统计光标之前的所有有效数字数量,忽略格式化符号,只关注用户实际输入的数字位置,而非字符位置。 - 光标位置映射:
mapCursorPosition函数遍历格式化后的字符串,找到对应数字位置的索引,自动跳过括号、空格、连字符,确保光标停留在可输入数字的位置。 - 异步更新光标:修改文本后通过
DispatchQueue.main.async延迟设置光标,确保SwiftUI完成文本更新后再调整光标位置,避免状态冲突。
兼容性说明
该方案支持iOS 16及以上版本,依赖TextField的selection绑定API(iOS 16引入)。
内容的提问来源于stack exchange,提问作者laconicman
相关产品推荐
相关产品推荐

