'SwiftUI: Always active textfield keyboard doesn't dismiss

I made a custom Textfield an always active textfield with the help of some tutorials, meaning that user can tap "next on keyboard" and commits the message and continue doing so without hiding the keyboard, but now the keyboard is stuck, doesn't close when tapped outside, I tried everything I saw online, some of them were basically triggering UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)

Note: my minimum iOS requirements is iOS 14 which restricts some of the new functions that helps with my issue in iOS15.

that didn't work with that Custom Textfield, its like its overriding those functions.

here is the textfield code:

    import SwiftUI

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(contentTertiary)]
        )
        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 ?? ""
        }
    }
}

used it on sample SwiftUI Sheet view and inside the .sheet view:

AlwaysActiveTextField(
                        placeholder: "Add...",
                        text: $newItemName,
                        focusable: $fieldFocus,
                        returnKeyType: .next,
                        isSecureTextEntry: false,
                        tag: 0,
                        onCommit: {
                            if !newItemName.isEmpty {
                                let newChecklistItem = ChecklistItem(
                                    shotId: shotlistViewModel.shot.id,
                                    name: self.newItemName,
                                    isChecked: false,
                                    description: ""
                                )
                                self.checklistItems.append(newChecklistItem)
                                if !offlineMode {
                                    self.viewModel.addChecklistItem(newChecklistItem)
                                }
                                self.newItemName = ""
                            }
                        }
                    )


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source