SwiftUI default focus on TextField/TextEditor when view is loaded

Let's say I have a SwiftUI view that's a compose experience, like composing a new email in Mail, an iMessage in Messages, or a Tweet in Twitter.

When the view is loaded, I want the soft keyboard to appear automatically so that the user can type immediately without needing to manually put focus in the TextField or TextEditor by tapping on it.

With the new @FocusState feature in iOS 15, I can programmatically put focus on different TextFields when the user interacts elsewhere in the UI (like pressing a button), but I can't seem to set a default focus.

I tried initializing focus in the SwiftUI view's init method as well as the onAppear method attached to other parts of the view, but this didn't seem to have any effect.

How can I put default focus on a TextField or TextEditor when a view is loaded?

I also wasn't able to get it to work using init, however I'm not really surprised by that given how other property wrappers like @State work.

I was eventually able to get it working using onAppear, however after a bunch of testing I found that I'm able to get it work with simple Views but as the view gets more complex the behavior gets confusing/inconsistent. For example I have included the code for a View below that:

  • Will set the focus state that will focus a field when the view appears.
  • Will not set the focus state if you you uncomment the NavigationView.
  • Will set the focus state if it is the destination of a NavigationLink inside a NavigationView.
  • Will not set the focus state if you follow a NavigationLink inside the view then go back.

I'm not sure what the intended behavior is, however I'm guessing that this isn't it.

Here is my sample View:

struct ComposeView: View {
  enum FocusField: Hashable {
    case field
  }

  @FocusState private var focusedField: FocusField?

  @State var text: String = ""

  var body: some View {
    //NavigationView {
      VStack {
        TextField("textField", text: $text)
          .focused($focusedField, equals: .field)
          .onAppear {
            self.focusedField = .field
        }
      }
    //}
  }

Noticed that for macOS there's a prefersDefaultFocus(_:in:). Which combined with some of the WWDC notes from Javier over here https://www.swiftui-lab.com/random-lessons#focus-2 leads me think that directly twiddling FocusState to set the default focus is seen as an anti-pattern.

However, until prefersDefaultFocus (or similar) arrives on iOS I've found that it can be made to work by adding a (hacky) small delay to setting it after onAppear gets executed, e.g. something like this ...

.onAppear {
      DispatchQueue.main.asyncAfter(deadline: .now() + 1) {  /// Anything over 0.5 seems to work
            self.focusedField = .field
       }
}
13

Use .task to assign the initial focused field instead of using .onAppear

TextField("Placeholder", text: $text)
          .focused($focusedField, equals: .field)
          .task {
            self.focusedField = .field
          }

I filed an FB about this if anyone else wants to refer to it: FB9676178

You should have some delay.

@avitzur, where do you put that call in the view? I have tried it in .onAppear (and .task) on my NavigationView and then my app crashes. Any hints?

Updated

I added

.onAppear() {

                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5){ nameIsFocused = true }

                        } on my TextField, and now it works. Thank you avitzur

Thank you, that is very helpful.

SwiftUI default focus on TextField/TextEditor when view is loaded
 
 
Q