SwiftUI,从工具栏按钮更改 TextField 字符串

问题描述 投票:0回答:1

在 SwiftUI 中,如何在 TextField 的当前光标位置插入字符串? 在下面的示例中,我想在工具栏按钮事件中将字符串从

1234
更改为
12+34

@State private var inputText: String = "1234"

public var body: some View {
    VStack {
        TextField("Input text", text: $inputText)
        .toolbar {
            ToolbarItemGroup(placement: .keyboard) {
                HStack {
                    Button("+") {
                        //
                        // Here I want to insert "+" at the current cursor position.
                        //
                    }
                }
            }
        }
    }
}
string swiftui toolbar
1个回答
0
投票

您已经可以访问

inputText
,所以这是确定当前光标位置的问题。正如 这篇 StackOverflow 帖子 中所见,目前这在纯 SwiftUI 中是不可能的。但是,使用自定义实现,您可以通过
String.Index
NSTextRange
实现您想要实现的目标。但是,我目前不知道在 SwiftUI 和 AppKit directy 之间传递这个值的方法,所以我下面的实现使用
ObservableObject
单例

TextHolder

class TextHolder: ObservableObject{
    ///The shared instance of `TextHolder` for access across the frameworks.
    public static let shared = TextHolder()
    
    ///The currently user selected text range.
    @Published var selectedRange: NSRange? = nil
    
    //NOTE: You can comment the next variable out if you do not need to update cursor location
    ///Whether or not SwiftUI just changed the text
    @Published var justChanged = false
}

一些解释:

  • TextHolder.shared
    这里是单例,方便我们通过SwiftUI和AppKit来访问它
  • selectedRange
    是用户所选文本的实际
    NSRange
    。我们将使用
    location
    属性来添加文本,因为这是用户光标所在的位置。
  • justChanged
    是反映加号按钮是否被点击的属性,如果是,我们需要将用户的光标向前移动一个点(到加号前面)。

TextFieldRepresentable

struct TextFieldRepresentable: NSViewRepresentable{
    
    
    ///This is an `NSTextField` for use in SwiftUI
    typealias NSViewType = NSTextField
    
    ///The placeholder to be displayed when `text` is empty
    var placeholder: String = ""
    
    ///This is the text that the `TextFieldRepresentable` will display and change.
    @Binding var text: String
    
    
    func makeNSView(context: Context) -> NSTextField {
        let textField = NSTextField()
        //Set the placeholder for when there is no text
        textField.placeholderString = placeholder
        //Set the TextField delegate
        textField.delegate = context.coordinator
        
        return textField
    }
    
    func updateNSView(_ nsView: NSTextField, context: Context) {
        //Update the actual TextField
        nsView.stringValue = text
        //NOTE: You can comment this out if you do not need to update the cursor location
        DispatchQueue.main.async {
            //Move the cursor forward one if SwiftUI just changed the value
            if TextHolder.shared.justChanged{
                nsView.currentEditor()?.selectedRange.location += 1
                TextHolder.shared.justChanged = false
            }
        }
        //END commentable area
        
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, NSTextFieldDelegate {
        var parent: TextFieldRepresentable
        
        init(_ parent: TextFieldRepresentable) {
            self.parent = parent
        }
        
        func controlTextDidChange(_ obj: Notification) {
            //To avoid the "NSHostingView is being laid out reentrantly while rendering its SwiftUI content." error
            DispatchQueue.main.async {
                
                
                //Ensure we can get the current editor
                //If not, handle the error appropriately
                if let textField = obj.object as? NSTextField, let editor = textField.currentEditor(){
                    //Update the parent's text, so SwiftUI knows the new value
                    self.parent.text = textField.stringValue
                    //Set the property
                    TextHolder.shared.selectedRange = editor.selectedRange
                } else {
                    //Handle errors - we could not get the editor
                    print("Could not get the current editor")
                }
            }
        }
    }
    
}

最后,示例
View
用法:

struct ContentView: View {
    @State private var inputText: String = "1234"
    @ObservedObject var holder = TextHolder.shared
    public var body: some View {
        VStack {
            TextFieldRepresentable(placeholder: "Input text", text: $inputText)
            .toolbar {
                ToolbarItem(id: UUID().uuidString, placement: .automatic) {
                    HStack {
                        Button("+") {
                               insertPlus()
                        }
                    }
                }
            }
        }
    }
    
    ///Inserts the plus character at the selectedRange/
    func insertPlus(){
        //First, we will check if our range is not nil
         guard let selectedRange = holder.selectedRange else {
             //Handle errors, as we could not get the selected range
             print("The holder did not contain a selected range")
             return
         }
         let endPos = inputText.index(inputText.startIndex, offsetBy: selectedRange.location) // End of the selected range position
         //Insert the text
         inputText.insert(contentsOf: "+", at: endPos)
        //Necessary to move cursor to correct location
        TextHolder.shared.justChanged = true
    }
}

这是一个实际的例子: 此代码已通过 Xcode 14.2/macOS 13.1 测试。

来源

© www.soinside.com 2019 - 2024. All rights reserved.