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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 10:05:56