SwiftUI:提交后留在同一个 TextField 上?

Posted

技术标签:

【中文标题】SwiftUI:提交后留在同一个 TextField 上?【英文标题】:SwiftUI: stay on same TextField after on commit? 【发布时间】:2021-09-17 14:49:27 【问题描述】:

即使用户点击键盘上的 Return 键,在 SwiftUI 中是否可以将打字光标保持在同一个 Textfield 上?

这是我的代码:

struct RowView: View 
    @Binding var checklistItem: ChecklistItem
    @ObservedObject var checklist = Checklist()
    @ObservedObject var viewModel: ChecklistViewModel
    var body: some View 
        HStack 
            Button 
                self.checklistItem.isChecked.toggle()
                self.viewModel.updateChecklist(checklistItem)
             label: 
                Circle()
                    .strokeBorder(checklistItem.isChecked ? checklistSelected : contentPrimary, lineWidth: checklistItem.isChecked ? 6 : 2)
                    .foregroundColor(backgroundSecondary)
                    .clipShape(Circle())
                    .frame(width: 16, height: 16)
            .buttonStyle(BorderlessButtonStyle())
            // swiftlint:disable trailing_closure
            TextField(
                "Add...",
                text: $checklistItem.name,
                onCommit: 
                    do 
                        if !checklistItem.name.isEmpty 
                            self.viewModel.updateChecklist(checklistItem)
                            self.checklistItem.name = checklistItem.name
                        
                    
                
            )
            // swiftlint:enable trailing_closure
            .foregroundColor(checklistItem.isChecked ? contentTertiary : contentPrimary)
            Spacer()
        
    

因此,在用户点击键盘上的返回键后,TextField() onCommit 应该会正常激活,但光标会停留在同一个文本字段中,以便用户可以继续输入新元素。

【问题讨论】:

这可能会有所帮助:***.com/questions/61669540/… 他们如何退出呢?如果键盘一直在,并且无法通过返回关闭,那么用户如何继续? @George 在视图外点击 【参考方案1】:

iOS 15+

您可以使用@FocusState,并在提交时立即将TextField 设置为再次获得焦点。

例子:

struct ContentView: View 
    @State private var text = "Hello world!"
    @FocusState private var isFieldFocused: Bool

    var body: some View 
        Form 
            TextField("Field", text: $text, onCommit: 
                isFieldFocused = true
                print("onCommit")
            )
            .focused($isFieldFocused)
        
    

结果:

【讨论】:

太棒了,ios 14 中是否有与之等效的版本,因为这是我的最低应用版本,即使它必须是定制的 @Hussein 可能是使用 SwiftUI-Introspect 然后设置 TextField 成为第一响应者。 请注意,从 Xcode 13 开始,焦点行为在 Xcode Previews 中不起作用,但在模拟器中起作用。【参考方案2】:

我可以通过创建自定义 TextField 类在 iOS 14 中实现这一点:

struct AlwaysActiveTextField: UIViewRepresentable 
    let placeholder: String
    @Binding var text: String
    var focusable: Binding<[Bool]>?
    var returnKeyType: UIReturnKeyType = .next
    var autocapitalizationType: UITextAutocapitalizationType = .none
    var keyboardType: UIKeyboardType = .default
    var isSecureTextEntry: Bool
    var tag: Int
    var onCommit: () -> Void

    func makeUIView(context: Context) -> UITextField 
        let activeTextField = UITextField(frame: .zero)
        activeTextField.delegate = context.coordinator
        activeTextField.placeholder = placeholder
        activeTextField.font = .systemFont(ofSize: 14)
        activeTextField.attributedPlaceholder = NSAttributedString(
            string: placeholder,
            attributes: [NSAttributedString.Key.foregroundColor: UIColor(contentSecondary)]
        )
        activeTextField.returnKeyType = returnKeyType
        activeTextField.autocapitalizationType = autocapitalizationType
        activeTextField.keyboardType = keyboardType
        activeTextField.isSecureTextEntry = isSecureTextEntry
        activeTextField.textAlignment = .left
        activeTextField.tag = tag
        // toolbar
        if keyboardType == .numberPad  // keyboard does not have next so add next button in the toolbar
            var items = [UIBarButtonItem]()
            let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)

            let toolbar: UIToolbar = UIToolbar()
            toolbar.sizeToFit()
            let nextButton = UIBarButtonItem(title: "Next", style: .plain, target: context.coordinator, action: #selector(Coordinator.showNextTextField))
            items.append(contentsOf: [spacer, nextButton])
            toolbar.setItems(items, animated: false)
            activeTextField.inputAccessoryView = toolbar
        
        // Editin listener
        activeTextField.addTarget(context.coordinator, action: #selector(Coordinator.textFieldDidChange(_:)), for: .editingChanged)

        return activeTextField
    

    func updateUIView(_ uiView: UITextField, context: Context) 
        uiView.text = text

        if let focusable = focusable?.wrappedValue 
            if focusable[uiView.tag]  // set focused
                uiView.becomeFirstResponder()
             else  // remove keyboard
                uiView.resignFirstResponder()
            
        
    

    func makeCoordinator() -> Coordinator 
        Coordinator(self)
    

    final class Coordinator: NSObject, UITextFieldDelegate 
        let activeTextField: AlwaysActiveTextField
        var hasEndedViaReturn = false
        weak var textField: UITextField?

        init(_ activeTextField: AlwaysActiveTextField) 
            self.activeTextField = activeTextField
        

        func textFieldDidBeginEditing(_ textField: UITextField) 
            self.textField = textField
            guard let textFieldCount = activeTextField.focusable?.wrappedValue.count else  return 
            var focusable: [Bool] = Array(repeating: false, count: textFieldCount) // remove focus from all text field
            focusable[textField.tag] = true // mark current textField focused
            activeTextField.focusable?.wrappedValue = focusable
        
        // work around for number pad
        @objc
        func showNextTextField() 
            if let textField = self.textField 
                _ = textFieldShouldReturn(textField)
            
        

        func textFieldShouldReturn(_ textField: UITextField) -> Bool 
            hasEndedViaReturn = true
            guard var focusable = activeTextField.focusable?.wrappedValue else 
                textField.resignFirstResponder()
                return true
            
            focusable[textField.tag] = true // mark current textField focused
            activeTextField.focusable?.wrappedValue = focusable
            activeTextField.onCommit()
            return true
        

        func textFieldDidEndEditing(_ textField: UITextField) 
            if !hasEndedViaReturn // user dismisses keyboard
                guard let textFieldCount = activeTextField.focusable?.wrappedValue.count else  return 
                // reset all text field, so that makeUIView cannot trigger keyboard
                activeTextField.focusable?.wrappedValue = Array(repeating: false, count: textFieldCount)
             else 
                hasEndedViaReturn = false
            
        
        @objc
        func textFieldDidChange(_ textField: UITextField) 
            activeTextField.text = textField.text ?? ""
        
    

并通过添加此@State 变量在 SwiftUI 视图中使用:

@State var fieldFocus: [Bool] = [false]

并在任何等待视图主体的地方添加文本字段代码:

AlwaysActiveTextField(
                            placeholder: "Add...",
                            text: $newItemName,
                            focusable: $fieldFocus,
                            returnKeyType: .next,
                            isSecureTextEntry: false,
                            tag: 0,
                            onCommit: 
                                print("any action you want on commit")
                            
                        )

【讨论】:

以上是关于SwiftUI:提交后留在同一个 TextField 上?的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI:

SwiftUI:TextField文本输入

在 SwiftUI 中捕获绑定的最后一次更改 / 组合

SwiftUI 如何获取对 List 内编辑 Textfield 的参考?

SwiftUI如何让绑定到同一个状态的多个TextField呈现出不同输入行为

SwiftUI如何让绑定到同一个状态的多个TextField呈现出不同输入行为