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 filed an FB about this if anyone else wants to refer to it: FB9676178

  • Thank you, that is very helpful.

Add a Comment

Replies

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
       }
}
  • This works

  • Thanks, I tried to give .now() + 0.5 doesn't work, but .now() + 1 works.

  • Thank you!!

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

TextField("Placeholder", text: $text)
          .focused($focusedField, equals: .field)
          .task {
            self.focusedField = .field
          }
  • This doesn't work for me. I hope they fix this bug — one of the most important use cases of focus is to auto focus a field when you load a view.

  • Yeah, this isn't working for me either :-(

  • Not working

You should have some delay.

  • I added a 1 second delay and it works from onappear. Silly bug.

  • Verified: DispatchQueue.main.asyncAfter(deadline: .now() + 0.5){ isFocused = true } works where setting it immediately fails.

Add a Comment

@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