Advanced SwiftUI TextField: Events, Focus, Keyboard

Published on

Get weekly handpicked updates on Swift and SwiftUI!

This article will explore the experiences, techniques, and considerations related to SwiftUI TextField events, focus switching, keyboard settings, and more.

Events

onEditingChanged

When the TextField gains focus (enters the editable state), onEditingChanged calls the given method and passes the value true; when the TextField loses focus, it calls the method again and passes the value false.

Swift
struct OnEditingChangedDemo:View{
    @State var name = ""
    var body: some View{
        List{
            TextField("name:",text:$name,onEditingChanged: getFocus)
        }
    }

    func getFocus(focused:Bool) {
        print("get focus:\(focused ? "true" : "false")")
    }
}

The name of this parameter is easy to cause ambiguity for users. Do not use onEditingChanged to determine whether the user has entered content.

In iOS 15, the newly added constructor that supports ParseableFormatStyle does not provide this parameter. Therefore, for TextFields that use the new Formatter, other means need to be used to determine whether they have gained or lost focus.

onCommit

onCommit is triggered when the user presses (or clicks) the return key during the input process (cannot be triggered by code simulation). If the user does not click the return key (such as directly switching to other TextFields), onCommit will not be triggered. At the same time as onCommit is triggered, the TextField will also lose focus.

Swift
struct OnCommitDemo:View{
    @State var name = ""
    var body: some View{
        List{
            TextField("name:",text: $name,onCommit: {print("commit")})
        }
    }
}

If you need to check the user’s input after they enter it, it’s best to use onCommit and onEdtingChanged together. If you want to process the user’s input data in real time, please refer to Advanced SwiftUI TextField: Events, Focus, Keyboard.

onCommit also applies to SecureField.

In iOS 15, the newly added constructor that supports ParseableFormatStyle does not provide this parameter. You can use the newly added onSubmit to achieve the same effect.

onSubmit

onSubmit is a new feature in SwiftUI 3.0. While onCommit and onEditingChanged describe the state of each TextField individually, onSubmit allows for unified management and scheduling of multiple TextFields within a view.

Swift
// Definition of onSubmit
extension View {
    public func onSubmit(of triggers: SubmitTriggers = .text, _ action: @escaping (() -> Void)) -> some View
}

The following code will implement the same behavior as onCommit:

Swift
struct OnSubmitDemo:View{
    @State var name = ""
    var body: some View{
        List{
            TextField("name:",text: $name)
                .onSubmit {
                    print("commit")
                }
        }
    }
}

The triggering condition of onSubmit is the same as onCommit, which requires the user to actively click “return”.

onSubmit is also applicable to SecureField.

Scope and Nesting

The implementation of onSubmit is based on setting the environment value TriggerSubmitAction (not yet available to developers), so onSubmit has a scope (can be passed up the view tree) and can be nested.

Swift
struct OnSubmitDemo: View {
    @State var text1 = ""
    @State var text2 = ""
    @State var text3 = ""
    var body: some View {
        Form {
            Group {
                TextField("text1", text: $text1)
                    .onSubmit { print("text1 commit") }
                TextField("text2", text: $text2)
                    .onSubmit { print("text2 commit") }
            }
            .onSubmit { print("textfield in group commit") }
            TextField("text3", text: $text3)
                .onSubmit { print("text3 commit") }
        }
        .onSubmit { print("textfield in form commit1") }
        .onSubmit { print("textfield in form commit2") }
    }
}

When TextField (text1) is committed, the console output is:

textfield in form commit2
textfield in form commit1
textfield in group commit
text1 commit

Please note that the calling order is from outer to inner.

Limited scope

You can use submitScope to block the scope (limit further propagation on the view tree). For example, in the code above, add submitScope after Group

            Group {
                TextField("text1", text: $text1)
                    .onSubmit { print("text1 commit") }
                TextField("text2", text: $text2)
                    .onSubmit { print("text2 commit") }
            }
            .submitScope()  // block the scope
            .onSubmit { print("textfield in group commit") }

When TextField1 commits, the console outputs:

text1 commit

At this point, the scope of onSubmit will be limited to within the Group.

When there are multiple TextFields in the view, a great user experience can be achieved by combining onSubmit and FocusState (introduced below).

Support for searchable

In iOS 15, the new search box also triggers onSubmit when “return” is clicked, but triggers need to be set to search:

Swift
struct OnSubmitForSearchableDemo:View{
    @State var name = ""
    @State var searchText = ""
    var body: some View{
        NavigationView{
            Form{
                TextField("name:",text:$name)
                    .onSubmit {print("textField commit")}
            }
            .searchable(text: $searchText)
            .onSubmit(of: .search) { //
                print("searchField commit")
            }
        }
    }
}

It should be noted that SubmitTriggers is of type OptionSet. onSubmit will continuously pass the values contained in SubmitTriggers within the environment in the view tree. When the received SubmitTriggers value is not included in the SubmitTriggers set in onSubmit, the passing will be terminated. Simply put, onSubmit(of:.search) will block the commit state generated by TextFiled, and vice versa.

For example, in the above code, if we add another onSubmt(of:.text) after searchable, we won’t be able to respond to the commit event of the TextField.

Swift
            .searchable(text: $searchText)
            .onSubmit(of: .search) {
                print("searchField commit1")
            }
            .onSubmit {print("textField commit")} //cannot be triggered, blocked by search

Therefore, when handling both TextField and search box, special attention needs to be paid to their calling order.

The following code can be used to support both TextField and search box in one onSubmit:

Swift
.onSubmit(of: [.text, .search]) {
  print("Something has been submitted")
}

The following code won’t trigger either because onSubmit(of:search) is placed before searchable.

Swift
        NavigationView{
            Form{
                TextField("name:",text:$name)
                    .onSubmit {print("textField commit")}
            }
            .onSubmit(of: .search) { // won't trigger
                print("searchField commit1")
            }
            .searchable(text: $searchText)
        }

Focus

Before iOS 15 (Monterey), SwiftUI did not provide a way to get focus for TextField (e.g., becomeFirstResponder). For a long time, developers had to resort to non-SwiftUI methods to achieve similar functionality.

In SwiftUI 3.0, Apple has provided developers with a much better than expected solution, similar to onSubmit, which allows for a higher-level view hierarchy to uniformly determine and manage the focus of text fields within a view.

Basic Usage

SwiftUI provides a new FocusState property wrapper to help us determine if a TextField within this view has focus. We can associate FocusState with a specific TextField by using focused.

Swift
struct OnFocusDemo:View{
    @FocusState var isNameFocused:Bool
    @State var name = ""
    var body: some View{
        List{
            TextField("name:",text:$name)
                .focused($isNameFocused)
        }
        .onChange(of:isNameFocused){ value in
            print(value)
        }
    }
}

The above code will set isNameFocused to true when TextField gains focus and false when it loses focus.

For multiple TextFields in the same view, you can create multiple FocusStates to associate with each corresponding TextField. For example:

Swift
struct OnFocusDemo:View{
    @FocusState var isNameFocused:Bool
    @FocusState var isPasswordFocused:Bool
    @State var name = ""
    @State var password = ""
    var body: some View{
        List{
            TextField("name:",text:$name)
                .focused($isNameFocused)
            SecureField("password:",text:$password)
                .focused($isPasswordFocused)
        }
        .onChange(of:isNameFocused){ value in
            print(value)
        }
        .onChange(of:isPasswordFocused){ value in
            print(value)
        }
    }
}

The above method becomes cumbersome when there are more TextFields in the view and is not conducive to unified management. Fortunately, FocusState not only supports Boolean values, but also supports any hash type. We can use an enum that conforms to the Hashable protocol to manage the focus of multiple TextFields in the view. The following code will achieve the same function as the above:

Swift
struct OnFocusDemo:View{
    @FocusState var focus:FocusedField?
    @State var name = ""
    @State var password = ""
    var body: some View{
        List{
            TextField("name:",text:$name)
                .focused($focus, equals: .name)
            SecureField("password:",text:$password)
                .focused($focus,equals: .password)
        }
        .onChange(of: focus, perform: {print($0)})
    }

    enum FocusedField:Hashable{
        case name,password
    }
}

Make a specified TextField gain focus immediately after displaying a view

Using FocusState, it is easy to make a specified TextField gain focus and pop up the keyboard immediately after displaying a view:

Swift
struct OnFocusDemo:View{
    @FocusState var focus:FocusedField?
    @State var name = ""
    @State var password = ""
    var body: some View{
        List{
            TextField("name:",text:$name)
                .focused($focus, equals: .name)
            SecureField("password:",text:$password)
                .focused($focus,equals: .password)
        }
        .onAppear{
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5){
                focus = .name
            }
        }
    }

    enum FocusedField:Hashable{
        case name,password
    }
}

Assigning values during view initialization is invalid. Even in onAppear, there must be some delay to allow TextField to focus (no delay is required on iOS 16).

Switching focus between multiple TextFields

By combining focused and onSubmit, we can achieve the effect of automatically switching focus to the next TextField when the user finishes inputting in one TextField (by clicking return).

Swift
struct OnFocusDemo:View{
    @FocusState var focus:FocusedField?
    @State var name = ""
    @State var email = ""
    @State var phoneNumber = ""
    var body: some View{
        List{
            TextField("Name:",text:$name)
                .focused($focus, equals: .name)
                .onSubmit {
                    focus = .email
                }
            TextField("Email:",text:$email)
                .focused($focus,equals: .email)
                .onSubmit {
                    focus = .phone
                }
            TextField("PhoneNumber:",text:$phoneNumber)
                .focused($focus, equals: .phone)
                .onSubmit {
                    if !name.isEmpty && !email.isEmpty && !phoneNumber.isEmpty {
                        submit()
                    }
                }
        }
    }

    private func submit(){
        // submit all infos
        print("submit")
    }

    enum FocusedField:Hashable{
        case name,email,phone
    }
}

The above code can also be transformed into the following format by utilizing the onSubmit passing feature:

Swift
        List {
            TextField("Name:", text: $name)
                .focused($focus, equals: .name)
            TextField("Email:", text: $email)
                .focused($focus, equals: .email)
            TextField("PhoneNumber:", text: $phoneNumber)
                .focused($focus, equals: .phone)
        }
        .onSubmit {
            switch focus {
            case .name:
                focus = .email
            case .email:
                focus = .phone
            case .phone:
                if !name.isEmpty, !email.isEmpty, !phoneNumber.isEmpty {
                    submit()
                }
            default:
                break
            }
        }

By combining screen buttons (such as the assistive keyboard view) or keyboard shortcuts, we can also change the focus forward or jump to another specific TextField.

Using Keyboard Shortcuts to Obtain Focus

When a view has multiple TextFields (including SecureFields), we can directly use the Tab key to switch focus in the TextField in order. However, SwiftUI does not directly provide the ability to use keyboard shortcuts to make a certain TextField obtain focus. By combining FocusState and keyboardShortcut, this ability can be obtained on iPad and MacOS.

Create a focused that supports keyboard shortcut binding:

Swift
public extension View {
    func focused(_ condition: FocusState<Bool>.Binding, key: KeyEquivalent, modifiers: EventModifiers = .command) -> some View {
        focused(condition)
            .background(Button("") {
                condition.wrappedValue = true
            }
            .keyboardShortcut(key, modifiers: modifiers)
            .hidden()
            )
    }

    func focused<Value>(_ binding: FocusState<Value>.Binding, equals value: Value, key: KeyEquivalent, modifiers: EventModifiers = .command) -> some View where Value: Hashable {
        focused(binding, equals: value)
            .background(Button("") {
                binding.wrappedValue = value
            }
            .keyboardShortcut(key, modifiers: modifiers)
            .hidden()
            )
    }
}

Code invocation:

Swift
struct ShortcutFocusDemo: View {
    @FocusState var focus: FouceField?
    @State private var email = ""
    @State private var address = ""
    var body: some View {
        Form {
            TextField("email", text: $email)
                .focused($focus, equals: .email, key: "t")
            TextField("address", text: $address)
                .focused($focus, equals: .address, key: "a", modifiers: [.command, .shift,.option])
        }
    }

    enum FouceField: Hashable {
        case email
        case address
    }
}

When the user enters ⌘ + T, the TextField responsible for email will receive focus. When the user enters ⌘ + ⌥ + ⇧ + A, the TextField responsible for address will receive focus.

The above code does not perform well on the iPad simulator (sometimes unable to activate), please test it on a real device.

Create your own onEditingChanged

The best option for determining the focus status of a single TextField is still to use onEditingChanged, but for some cases where onEditingChanged cannot be used (such as with new Formatters), we can use FocusState to achieve similar effects.

  • Determine the focus status of a single TextField
Swift
public extension View {
    func focused(_ condition: FocusState<Bool>.Binding, onFocus: @escaping (Bool) -> Void) -> some View {
        focused(condition)
            .onChange(of: condition.wrappedValue) { value in
                onFocus(value == true)
            }
    }
}

Usage:

Swift
struct onEditingChangedFocusVersion:View{
    @FocusState var focus:Bool
    @State var price = 0
    var body: some View{
        Form{
            TextField("Price:",value:$price,format: .number)
                .focused($focus){ focused in
                    print(focused)
                }
        }
    }
}
  • Check multiple TextFields

To avoid multiple calls when a TextField loses focus, we need to save the FocusState value of the last focused TextField in the view hierarchy.

Swift
public extension View {
    func storeLastFocus<Value: Hashable>(current: FocusState<Value?>.Binding, last: Binding<Value?>) -> some View {
        onChange(of: current.wrappedValue) { _ in
            if current.wrappedValue != last.wrappedValue {
                last.wrappedValue = current.wrappedValue
            }
        }
    }

    func focused<Value>(_ binding: FocusState<Value>.Binding, equals value: Value, last: Value?, onFocus: @escaping (Bool) -> Void) -> some View where Value: Hashable {
        return focused(binding, equals: value)
            .onChange(of: binding.wrappedValue) { focusValue in
                if focusValue == value {
                    onFocus(true)
                } else if last == value { // only call once
                    onFocus(false)
                }
            }
    }
}

Call:

Swift
struct OnFocusView: View {
    @FocusState private var focused: Focus?
    @State private var lastFocused: Focus?
    @State private var name = ""
    @State private var email = ""
    @State private var address = ""
    var body: some View {
        List {
            TextField("Name:", text: $name)
                .focused($focused, equals: .name, last: lastFocused) {
                    print("name:", $0)
                }
            TextField("Email:", text: $email)
                .focused($focused, equals: .email, last: lastFocused) {
                    print("email:", $0)
                }
            TextField("Address:", text: $address)
                .focused($focused, equals: .address, last: lastFocused) {
                    print("address:", $0)
                }
        }
        .storeLastFocus(current: $focused, last: $lastFocused) // save lastest focsed value
    }

    enum Focus {
        case name, email, address
    }
}

Keyboard

Using TextField inevitably involves dealing with the software keyboard. This section will introduce several examples related to the keyboard.

Keyboard Type

In iPhone, we can set the software keyboard type via keyboardType to facilitate user input or restrict the input character range.

For example:

struct KeyboardTypeDemo:View{
    @State var price:Double = 0
    var body: some View{
        Form{
            TextField("Price:",value:$price,format: .number.precision(.fractionLength(2)))
                .keyboardType(.decimalPad) //support decimal point numeric keyboard
        }
    }
}

https://cdn.fatbobman.com/image-20211020184520202.png

Currently, there are a total of 11 supported keyboard types, which are:

  • asciiCapable

ASCII character keyboard

  • numbersAndPunctuation

Numbers and punctuation

  • URL

Convenient for inputting URLs, including characters and ’.’, ’/’, ‘.com’

  • numberPad

Uses the region’s set of digits (0-9, ۰-۹, ०-९, etc.). Suitable for positive integers or PINs.

  • phonePad

Numbers and other symbols used in telephones, such as ’*#+’

  • namePhonePad

Convenient for entering text and phone numbers. Character state is similar to asciiCapable, and numeric state is similar to numberPad.

  • emailAddress

An asciiCapable keyboard convenient for entering ’@.’

  • decimalPad

A numberPad that includes a decimal point. See the image above for details.

  • twitter

An asciiCapable keyboard that includes ’@#‘.

  • webSearch

An asciiCapable keyboard that includes ’.’, with the ‘return’ key marked as ‘go’.

  • asciiCapableNumberPad

An asciiCapable keyboard that includes numbers.

Although Apple provides many keyboard modes to choose from, in some cases they may not meet the needs of users.

For example, numberPad and decimalPad do not have ’-’ and ‘return’. Prior to SwiftUI 3.0, we had to draw separately on the main view or use non-SwiftUI methods to solve the problem. In SwiftUI 3.0, however, the addition of native setting keyboard auxiliary views (detailed below) makes it no longer difficult to solve the above problems.

Get suggestions through TextContentType

When using certain iOS apps, when entering text, the software keyboard will automatically suggest the content we need to enter, such as phone numbers, emails, verification codes, and so on. These are all effects obtained through textContentType.

By setting the UITextContentType for the TextField, the system intelligently infers the content that may be wanted to be entered during input and displays suggestions.

The following code allows the use of the keychain when entering a password:

Swift
struct KeyboardTypeDemo: View {
    @State var password = ""
    var body: some View {
        Form {
            SecureField("", text: $password)
                .textContentType(.password)
        }
    }
}

https://cdn.fatbobman.com/image-20211020192033318.png

The following code will suggest similar email addresses from your contacts and emails when entering an email address:

Swift
struct KeyboardTypeDemo: View {
    @State var email = ""
    var body: some View {
        Form {
            TextField("", text: $email)
                .textContentType(.emailAddress)
        }
    }
}

https://cdn.fatbobman.com/image-20211020193117256.png

There are many types of UITextContentType that can be set, among which the commonly used ones include:

  • password
  • name options, such as name, givenName, middleName, etc.
  • address options, such as addressCity, fullStreetAddress, postalCode, etc.
  • telephoneNumber
  • emailAddress
  • oneTimeCode (verification code)

Testing textContentType is best done on a real device, as the simulator does not support certain items or provide enough information.

Dismissing the Keyboard

In some cases, after the user has finished entering text, we need to dismiss the software keyboard to make more display space available. Some keyboard types do not have a “return” button, so we need to use programming to make the keyboard disappear.

In addition, sometimes we may want the user to be able to dismiss the keyboard by clicking on other areas of the screen or scrolling the list, without having to click the “return” button, in order to improve the interaction experience. Again, we need to use programming to make the keyboard disappear.

  • Using FocusState to dismiss the keyboard

If the corresponding FocusState is set for the TextField, the keyboard can be dismissed by setting the value to “false” or “nil”.

Swift
struct HideKeyboardView: View {
    @State private var name = ""
    @FocusState private var nameIsFocused: Bool

    var body: some View {
        Form {
            TextField("Enter your name", text: $name)
                .focused($nameIsFocused)

            Button("dismiss Keyboard") {
                nameIsFocused = false
            }
        }
    }
}
  • Other situations

In most cases, we can directly use the methods provided by UIkit to dismiss the keyboard.

Swift
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)

For example, the following code will dismiss the keyboard when the user drags on the view:

Swift
struct ResignKeyboardOnDragGesture: ViewModifier {
    var gesture = DragGesture().onChanged { _ in
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }

    func body(content: Content) -> some View {
        content.gesture(gesture)
    }
}

extension View {
    func dismissKeyboard() -> some View {
        return modifier(ResignKeyboardOnDragGesture())
    }
}

struct HideKeyboardView: View {
    @State private var name = ""
    var body: some View {
        Form {
            TextField("Enter your name", text: $name)
        }
        .dismissKeyboard()
    }
}

Keyboard assistant view

Created through toolbar

In SwiftUI 3.0, we can create a keyboard assistant view (inputAccessoryView) by using ToolbarItem(placement: .keyboard, content: View).

With the input assistant view, we can solve many problems that were difficult to deal with before, and provide more means for interaction.

The code below adds a positive/negative conversion and confirmation button when inputting a floating point number:

Swift
import Introspect
struct ToolbarKeyboardDemo: View {
    @State var price = ""
    var body: some View {
        Form {
            TextField("Price:", text: $price)
                .keyboardType(.decimalPad)
                .toolbar {
                    ToolbarItem(placement: .keyboard) {
                        HStack {
                            Button("-/+") {
                                if price.hasPrefix("-") {
                                    price.removeFirst()
                                } else {
                                    price = "-" + price
                                }
                            }
                            .buttonStyle(.bordered)
                            Spacer()
                            Button("Finish") {
                                UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
                                // do something
                            }
                            .buttonStyle(.bordered)
                        }
                        .padding(.horizontal, 30)
                    }
                }
        }
    }
}

https://cdn.fatbobman.com/image-20211020202404796.png

Unfortunately, there are still some limitations to setting input accessory views through ToolbarItem:

  • Limited display content

    The height is fixed and cannot make use of the full display area of the accessory view. Similar to other types of toolbar, SwiftUI intervenes in the layout of the content.

  • Unable to set accessory views for multiple text fields separately within the same view

    More complex conditional syntax cannot be used in ToolbarItem. If the settings are made separately for different text fields, SwiftUI will merge all the content together for display.

Currently, SwiftUI’s intervention and processing of toolbar content is too excessive. The original intention was good, to help developers easily organize buttons and automatically optimize and achieve the best display effect on different platforms. However, the limitations of toolbar and ToolbarItem’s ResultBuilder are too many, making it difficult to perform more complex logical judgments within them. The logic of integrating keyboard accessory views into the toolbar is also somewhat confusing.

Creating through UIKit

At this stage, creating keyboard accessory views through UIKit is still the optimal solution for SwiftUI. Not only can it obtain complete control over the view display, but it can also separately set multiple text fields within the same view.

Swift
extension UIView {
    func constrainEdges(to other: UIView) {
        translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            leadingAnchor.constraint(equalTo: other.leadingAnchor),
            trailingAnchor.constraint(equalTo: other.trailingAnchor),
            topAnchor.constraint(equalTo: other.topAnchor),
            bottomAnchor.constraint(equalTo: other.bottomAnchor),
        ])
    }
}

extension View {
    func inputAccessoryView<Content: View>(@ViewBuilder content: @escaping () -> Content) -> some View {
        introspectTextField { td in
            let viewController = UIHostingController(rootView: content())
            viewController.view.constrainEdges(to: viewController.view)
            td.inputAccessoryView = viewController.view
        }
    }

    func inputAccessoryView<Content: View>(content: Content) -> some View {
        introspectTextField { td in
            let viewController = UIHostingController(rootView: content)
            viewController.view.constrainEdges(to: viewController.view)
            td.inputAccessoryView = viewController.view
        }
    }
}

Usage:

Swift
struct OnFocusDemo: View {
    @FocusState var focus: FocusedField?
    @State var name = ""
    @State var email = ""
    @State var phoneNumber = ""
    var body: some View {
        Form {
            TextField("Name:", text: $name)
                .focused($focus, equals: .name)
                .inputAccessoryView(content: accessoryView(focus: .name))

            TextField("Email:", text: $email)
                .focused($focus, equals: .email)
                .inputAccessoryView(content: accessoryView(focus: .email))

            TextField("PhoneNumber:", text: $phoneNumber)
                .focused($focus, equals: .phone)
        }
        .onSubmit {
            switch focus {
            case .name:
                focus = .email
            case .email:
                focus = .phone
            case .phone:
                if !name.isEmpty, !email.isEmpty, !phoneNumber.isEmpty {}
            default:
                break
            }
        }
    }
}

struct accessoryView: View {
    let focus: FocusedField?
    var body: some View {
        switch focus {
        case .name:
            Button("name") {}.padding(.vertical, 10)
        case .email:
            Button("email") {}.padding(.vertical, 10)
        default:
            EmptyView()
        }
    }
}

By the time of Swift UI 3.0, automatic keyboard avoidance for TextField has become quite mature. It can avoid covering the TextField being inputted in different types of views (such as List, Form, ScrollView), or in cases where auxiliary views or textContentType are used. If the height of the lift can be higher, perhaps the effect would be better, but it is currently somewhat cramped.

Custom SubmitLabel

By default, the submit behavior button on the keyboard for TextField (SecureField) is “return”. With the new “submitLabel” modifier introduced in SwiftUI 3.0, we can modify the “return” button to display text that is more relevant to the input context.

Swift
TextField("Username", text: $username)
            .submitLabel(.next)

https://cdn.fatbobman.com/image-20211021070740662.png

Currently supported types include:

  • continue
  • done
  • go
  • join
  • next
  • return
  • route
  • search
  • send

For example, in the previous code, we can set different corresponding displays for name, email, and phoneNumber.:

Swift
            TextField("Name:", text: $name)
                .focused($focus, equals: .name)
                .submitLabel(.next)

            TextField("Email:", text: $email)
                .focused($focus, equals: .email)
                .submitLabel(.next)

            TextField("PhoneNumber:", text: $phoneNumber)
                .focused($focus, equals: .phone)
                .submitLabel(.return)

Conclusion

Since SwiftUI 1.0, Apple has been continuously improving the functionality of TextField. In version 3.0, SwiftUI not only provides more native modifiers, but also provides integrated management logic such as FocusState and onSubmit. I believe that in another 2-3 years, the native functionality of SwiftUI’s main controls will be able to match the corresponding UIKit controls.

Weekly Swift & SwiftUI insights, delivered every Monday night. Join developers worldwide.
Easy unsubscribe, zero spam guaranteed