50

I didn't find any link or guide to change the "return" key to "done" when keyboard open for TextField in SwiftUI.

Is it possible now without customising UITextField?

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
Andro Developer
  • 1,323
  • 3
  • 12
  • 22
  • Does this answer your question? [Change 'Return' button function to 'Done' in swift in UITextView](https://stackoverflow.com/questions/31886720/change-return-button-function-to-done-in-swift-in-uitextview) – Chris Jan 31 '20 at 20:31
  • 5
    No, they are using UIKit and I am looking for an answer for SwiftUI – Andro Developer Feb 01 '20 at 04:48
  • I am looking for https://stackoverflow.com/questions/58121756/swiftui-how-to-navigate-through-textfields-by-clicking-on-return-button-from-k but without UIKit. – Andro Developer Feb 01 '20 at 04:54
  • Skip to Ty Irvine's Answer below, by far the simplest solution in my opinion – Niall Kehoe Nov 14 '20 at 14:36

5 Answers5

108

iOS 15

You can change the return key for each textField with a simple modifier called: submitLabel that takes the return key type:

enter image description here Image from WWDC21

Also, as you can see, you can have a callback to handle the return key press action just like the old textFieldShouldReturn function that is accessible by .onSubmit modifier.

⚠️ Seems like there is a bug on Xcode 13 beta 1 that prevents this modifier from working in some situations.

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
36

Update

This is no longer a good solution. In iOS 15 you can now add a .submitLabel(.done) modifier. Please see Mojtaba's answer for more details.


Old Answer

The best way I found was to just add the package Introspect to your project.

After doing so add in import Introspect anywhere in your project files.

Then add one of their View Modifiers to your Textfield to achieve what you want. I believe this is what you want though ⤵

.introspectTextField { textfield in
  textfield.returnKeyType = .done
}

What Does Introspect Do?

It exposes UIKit to be used in SwiftUI. So that Textfield object you see above there has access to all UITextfield functionality! This is a package though so be aware it could break in the future but for now this is a good option.

It's just nice because it saves you from making your own UIKit wrapper for every View

tyirvine
  • 1,861
  • 1
  • 19
  • 29
  • 3
    This is such an awesome pod, thank you for sharing it. I really dislike the workarounds for things like this and I think it's a genius approach until this stuff is accessible in SwiftUI more naturally. – David Greco May 24 '21 at 01:39
  • 1
    This should be selected as the correct answer. – dbDev Nov 09 '21 at 15:34
11

To get the go button on the keyboard. Try changing the keyboardtype to .webSearch.

// Tested on Xcode 12 beta 2 and iOS 14

.keyboardType(.webSearch)

karmjit singh
  • 229
  • 3
  • 14
  • Great answer. There are other great options for different use cases documented [here](https://developer.apple.com/documentation/uikit/uikeyboardtype) eg. `.keyboardType(.twitter)` and `.keyboardType(.phonePad)` – Sami Fouad Jan 20 '21 at 13:01
5

If somebody is looking for wrapping UITextField in UIViewRepresentable then I have some code to share:

struct CustomTextField: UIViewRepresentable {

    let tag: Int
    let placeholder: String
    let keyboardType: UIKeyboardType
    let returnVal: UIReturnKeyType

    @Binding var text: String
    @Binding var activeFieldTag: Int?
    var totalFields: Int = 0
    @Binding var isSecureTextEntry: Bool
    var textColor: UIColor = .pureWhite
    var font: UIFont = .nexaBold13
    var placeholderTextColor: UIColor = .pureWhite
    var placeholderFont: UIFont = .nexaLight13
    var onEditingChanged: (Bool) -> Void = { _ in }

    var lastActiveFieldTag: Int? {

        // Return, if no active field
        // (It also means textFieldShouldReturn not called yet OR called for last field)
        guard let activeFieldTag = activeFieldTag else {
            return nil
        }
        // Return previous field
        if activeFieldTag > 0 {
            return activeFieldTag - 1
        }
        // Return, if no previous field
        return nil
    }

    func makeUIView(context: Context) -> UITextField {
        let textField = UITextField(frame: .zero)
        textField.keyboardType = self.keyboardType
        textField.returnKeyType = self.returnVal
        textField.tag = self.tag
        textField.textColor = textColor
        textField.font = font
        textField.attributedPlaceholder = NSAttributedString(
            string: self.placeholder,
            attributes: [
                NSAttributedString.Key.foregroundColor: placeholderTextColor,
                NSAttributedString.Key.font: placeholderFont,
            ]
        )
        textField.delegate = context.coordinator
        textField.autocorrectionType = .no
        textField.isSecureTextEntry = isSecureTextEntry
        return textField
    }

    func updateUIView(_ textField: UITextField, context: Context) {
        if textField.text != self.text {
            textField.text = self.text
        }
        handleFirstResponder(textField)
        if textField.isSecureTextEntry != isSecureTextEntry {
            textField.isSecureTextEntry = isSecureTextEntry
        }
    }

    func handleFirstResponder(_ textField: UITextField) {

        // return if field is neither active nor last-active
        if tag != lastActiveFieldTag && tag != activeFieldTag {
            return
        }

        // return if field is already active
        if lastActiveFieldTag == activeFieldTag {
            return
        }

        // It creates problem in UI when we press the next button too fast and continuously on keyboard
        //        // Remove focus from last active field
        //        if lastActiveFieldTag == tag {
        //            uiView.removeFocus()
        //            return
        //        }

        // Give focus to active field
        if activeFieldTag == tag {
            textField.focus()
            return
        }
    }

    // Its called when pressing Next button on the keyboard
    // See textFieldShouldReturn
    func updateNextTag() {
        // There is no next field so set activeFieldTag to nil
        if tag + 1 == totalFields {
            activeFieldTag = nil
        } else {
            // Set next field tag as active
            activeFieldTag = tag + 1
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UITextFieldDelegate {

        var parent: CustomTextField

        init(_ textField: CustomTextField) {
            self.parent = textField
        }

        func updatefocus(textfield: UITextField) {
            textfield.focus()
        }

        func textFieldShouldReturn(_ textField: UITextField) -> Bool {

            // Give focus to next field
            parent.updateNextTag()
            parent.text = textField.text ?? ""

            // If there is no next active field then dismiss the keyboard
            if parent.activeFieldTag == nil {
                DispatchQueue.main.async {
                    textField.removeFocus()
                }
            } 

            return true
        }

        func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
            DispatchQueue.main.async {
                // To enable user to click on any textField while another is active
                self.parent.activeFieldTag = self.parent.tag
                self.parent.onEditingChanged(true)
            }
            return true
        }

        func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
            self.parent.text = textField.text ?? ""
            DispatchQueue.main.async {
                self.parent.onEditingChanged(false)
            }
            return true
        }

        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
            if let text = textField.text, let rangeExp = Range(range, in: text) {
                self.parent.text = text.replacingCharacters(in: rangeExp, with: string)
            }
            return true
        }
    }
}
Andro Developer
  • 1,323
  • 3
  • 12
  • 22
  • Why am I getting errors saying `Type 'UIColor' has no member 'pureWhite'` and Value of type 'UITextField' has no member 'focus'? Did something change – Peter Schorn Jul 17 '20 at 18:36
3

iOS 15.0+

macOS 12.0+, Mac Catalyst 15.0+, tvOS 15.0+, watchOS 8.0+

submitLabel(_:)

Sets the submit label for this view.

https://developer.apple.com

Form {
    TextField("Username", $viewModel.username)
        .submitLabel(.done)
}
mahan
  • 12,366
  • 5
  • 48
  • 83