SwiftUI中如何在TextField光标位置插入字符?
在SwiftUI的TextField光标位置插入字符
我添加了一个包含常用算术运算符的额外工具栏。
@State private var formulaText: String = "" var body: some View { TextField("Formula", text: $formulaText) .keyboardType(.asciiCapable) .autocorrectionDisabled() .toolbar { ToolbarItemGroup(placement: .keyboard) { HStack(spacing: 5.0) { toolbarButtonImage(systemName: "plus") { formulaText += "+" } toolbarButtonImage(systemName: "minus") { formulaText += "-" } toolbarButtonImage(systemName: "multiply") { formulaText += "×" } toolbarButtonImage(systemName: "divide") { formulaText += "÷" } toolbarButtonText(text: "(") { formulaText += "(" } toolbarButtonText(text: ")") { formulaText += ")" } } } } }
如代码逻辑所示,当前运算符会被追加到文本末尾,这在光标位于末尾时正常工作,但当用户将光标定位到其他位置时,用户期望运算符插入到光标位置,却无法实现。
如何实现这一需求?如何在TextField的光标位置插入字符?
解决方案
SwiftUI原生TextField未直接暴露光标位置的操作API,需借助UIKit的UITextField能力实现光标定位插入。以下提供两种适配不同iOS版本的可行方案:
方案1:封装自定义TextField(兼容iOS 13+)
通过UIViewRepresentable封装UITextField,完全掌控文本内容与光标状态:
import SwiftUI struct CustomTextField: UIViewRepresentable { @Binding var text: String let placeholder: String @Binding var selectedRange: NSRange func makeUIView(context: Context) -> UITextField { let textField = UITextField() textField.placeholder = placeholder textField.keyboardType = .asciiCapable textField.autocorrectionType = .no textField.delegate = context.coordinator textField.addTarget(context.coordinator, action: #selector(Coordinator.textChanged), for: .editingChanged) return textField } func updateUIView(_ uiView: UITextField, context: Context) { if uiView.text != text { uiView.text = text } // 同步光标位置 guard let startPos = uiView.position(from: uiView.beginningOfDocument, offset: selectedRange.location), let endPos = uiView.position(from: startPos, offset: selectedRange.length) else { return } uiView.selectedTextRange = uiView.textRange(from: startPos, to: endPos) } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, UITextFieldDelegate { let parent: CustomTextField init(_ parent: CustomTextField) { self.parent = parent } @objc func textChanged(_ textField: UITextField) { parent.text = textField.text ?? "" // 更新选中范围记录 let start = textField.selectedTextRange?.start ?? textField.beginningOfDocument let end = textField.selectedTextRange?.end ?? textField.beginningOfDocument let location = textField.offset(from: textField.beginningOfDocument, to: start) let length = textField.offset(from: start, to: end) parent.selectedRange = NSRange(location: location, length: length) } } // 插入字符到光标位置的方法 func insertCharacter(_ char: String) { guard let textRange = Range(selectedRange, in: text) else { return } // 替换选中内容(无选中则直接插入) text.replaceSubrange(textRange, with: char) // 光标移到插入字符后方 selectedRange = NSRange(location: selectedRange.location + char.count, length: 0) } }
在主视图中使用该自定义TextField:
struct ContentView: View { @State private var formulaText: String = "" @State private var textFieldRange = NSRange(location: 0, length: 0) var body: some View { CustomTextField(text: $formulaText, placeholder: "Formula", selectedRange: $textFieldRange) .toolbar { ToolbarItemGroup(placement: .keyboard) { HStack(spacing: 5.0) { Button(action: { CustomTextField(text: $formulaText, placeholder: "", selectedRange: $textFieldRange).insertCharacter("+") }) { Image(systemName: "plus") } Button(action: { CustomTextField(text: $formulaText, placeholder: "", selectedRange: $textFieldRange).insertCharacter("-") }) { Image(systemName: "minus") } Button(action: { CustomTextField(text: $formulaText, placeholder: "", selectedRange: $textFieldRange).insertCharacter("×") }) { Image(systemName: "multiply") } Button(action: { CustomTextField(text: $formulaText, placeholder: "", selectedRange: $textFieldRange).insertCharacter("÷") }) { Image(systemName: "divide") } Button(action: { CustomTextField(text: $formulaText, placeholder: "", selectedRange: $textFieldRange).insertCharacter("(") }) { Text("(") } Button(action: { CustomTextField(text: $formulaText, placeholder: "", selectedRange: $textFieldRange).insertCharacter(")") }) { Text(")") } } } } } }
方案2:iOS 16+简化实现
若仅需支持iOS 16及以上,可结合@FocusState与UIResponder扩展获取当前活跃TextField:
import SwiftUI struct ContentView: View { @State private var formulaText: String = "" @FocusState private var isTextFieldFocused: Bool var body: some View { TextField("Formula", text: $formulaText) .keyboardType(.asciiCapable) .autocorrectionDisabled() .focused($isTextFieldFocused) .toolbar { ToolbarItemGroup(placement: .keyboard) { HStack(spacing: 5.0) { toolbarButtonImage(systemName: "plus") { insertCharacter("+") } toolbarButtonImage(systemName: "minus") { insertCharacter("-") } toolbarButtonImage(systemName: "multiply") { insertCharacter("×") } toolbarButtonImage(systemName: "divide") { insertCharacter("÷") } toolbarButtonText(text: "(") { insertCharacter("(") } toolbarButtonText(text: ")") { insertCharacter(")") } } } } } private func insertCharacter(_ char: String) { guard isTextFieldFocused, let textField = UIResponder.currentFirstResponder as? UITextField else { return } let start = textField.selectedTextRange?.start ?? textField.beginningOfDocument // 替换选中内容或插入到光标位置 textField.replace(textField.textRange(from: start, to: textField.selectedTextRange?.end ?? start)!, withText: char) // 同步绑定文本 formulaText = textField.text ?? "" // 光标移到插入字符后方 let newPos = textField.position(from: start, offset: char.count)! textField.selectedTextRange = textField.textRange(from: newPos, to: newPos) } // 复用原有工具栏按钮实现 private func toolbarButtonImage(systemName: String, action: @escaping () -> Void) -> some View { Button(action: action) { Image(systemName: systemName) } } private func toolbarButtonText(text: String, action: @escaping () -> Void) -> some View { Button(action: action) { Text(text) } } } // 扩展UIResponder获取当前第一响应者 extension UIResponder { static var currentFirstResponder: UIResponder? { _currentFirstResponder = nil UIApplication.shared.sendAction(#selector(UIResponder.findFirstResponder(_:)), to: nil, from: nil, for: nil) return _currentFirstResponder } private static weak var _currentFirstResponder: UIResponder? @objc private func findFirstResponder(_ sender: Any) { UIResponder._currentFirstResponder = self } }
说明
- 方案1兼容性强,适合需要覆盖iOS 13+的项目,通过封装实现完全的状态控制;
- 方案2代码更简洁,依赖iOS 16的FocusState API,适合新版本定向开发;
- 两种方案均支持替换选中文本+光标位置插入的常规交互逻辑,符合用户操作预期。
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

