iosswiftswiftui

How to use .onSubmit() with vertical TextField in SwiftUI?


I'm trying to have a vertically growing TextField in `SwiftUI but also have the software keyboard have a custom submission method.

This uses the new functionality of iOS 16's TextFields being able to take an axis as an argument for which way it should grow. See the docs here: https://developer.apple.com/documentation/swiftui/textfield/init(_:text:axis:)-8rujz.

Here's a sample ContentView showing the setup.

struct ContentView: View {

    @State var message: String = ""
    var body: some View {
        
        VStack {
            Text("Try to submit this using the blue send button on the software keyboard")
            TextField("Placeholder", text: $message, axis: .vertical)
                .onSubmit {
                    print("submission!")
                }
            .submitLabel(.send)
        }
    }
}

When you run this, you can see the TextField properly grows vertically, but even when you have a custom submission label, pressing the blue "send" button in the software keyboard on iOS just inserts a newline, rather than firing the .onSubmit

When using a hardware keyboard, pressing the return does run the code in .onSubmit, so this seemingly just a limitation of the software keyboard.


Solution

  • Update: Consider using UITextView representable like this. Solution below may not work in all scenarios.

    I modified previous solutions, because in my case they led to TextField's jumping back and forth, adding and instantly removing a new line after hitting return (or done). If you experience similar behaviour, I combined previous solutions with this idea, and it helped. So, basically, you need to find out whether "\n" was inserted somewhere, and switch FocusState before message itself changes.

    struct ContentView: View {
    
        @State var message: String = ""
        @FocusState private var isFocused: Bool
        
        var body: some View {
            VStack {
                TextField("Placeholder",
                          text: Binding(
                            get: { message },
                            set:
                                { (newValue, _) in
                                    if let _ = newValue.lastIndex(of: "\n") {
                                        isFocused = false
                                    } else {
                                        message = newValue
                                    }
                                }
                          ), axis: .vertical)
                    .submitLabel(.done)
                    .focused($isFocused)
            }
        }
    }