171

Here's my SwiftUI code:

struct ContentView : View {

    @State var showingTextField = false
    @State var text = ""

    var body: some View {
        return VStack {
            if showingTextField {
                TextField($text)
            }
            Button(action: { self.showingTextField.toggle() }) {
                Text ("Show")
            }
        }
    }
}

What I want is when the text field becomes visible, to make the text field become the first responder (i.e. receive focus & have the keyboard pop up).

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
Epaga
  • 38,231
  • 58
  • 157
  • 245
  • 41
    The fact that something basic like this isn't even supported shows how immature SwiftUI is at the moment. Look at the solutions below, it's ridiculous for something that is supposed to make GUI coding "simpler" – Joris Mans Feb 18 '21 at 07:51

22 Answers22

104

Using SwiftUI-Introspect, you can do:

TextField("", text: $value)
.introspectTextField { textField in
    textField.becomeFirstResponder()
}
aheze
  • 24,434
  • 8
  • 68
  • 125
Joshua Kifer
  • 1,717
  • 3
  • 12
  • 14
  • 1
    Thanks a lot for your help. Good to know that it works also with SecureField(). – Tristan Clet Dec 22 '19 at 11:13
  • 2
    This Cocoapod worked for me too. I suspect the only reason this wasn't the accepted answer is that this question was asked and answered in June (WWDC19) long before SwiftUI-Introspect was created (late November 2019). – dmzza Dec 27 '19 at 05:06
  • This introspector pod is awesome, works perfectly in real phone, however in simulator whole view will lose responding if it contains a textfield which requires to be first responder. – ZhouX Jan 04 '20 at 05:38
  • 3
    If you have style modifiers on the TextField, you often have to add the introspectTextField modifier after the styles, as the order is important. – user1909186 Jan 29 '20 at 23:52
  • 1
    If I have two text fields and I do it like this then everything is cool until I tap into the second text field. As soon as I tap a keyboard button the respective character is added in the first text field that I have made the first responder. – Schnodderbalken Feb 21 '20 at 12:58
  • 1
    I noticed the same problem as Schnodderbalken – Ferdinand Rios Feb 25 '20 at 17:15
  • 1
    @Schnodderbalken The order of the modifiers is VERY important. For example, if you have .padding(.all), try moving the .introspectTextField { tf in tf.becomeFirstResponder() } up before the .padding(.all). If you have issues, let me know. – user1909186 Mar 21 '20 at 02:36
  • @Schnodderbalken Also, there may be a bug introduced in the latest release 0.1.0. Try using the previous release, 0.6.0. https://github.com/siteline/SwiftUI-Introspect/issues/19#issue-585409519 – user1909186 Mar 21 '20 at 02:44
  • Sorry 0.0.6 is the version not 0.6.0. Don't forget to clean the project. – user1909186 Mar 21 '20 at 02:50
  • 1
    It works great for English. However, it refreshes every single type for other languages that requires user to select a word from keyboard. For example, in Chinese, the word picker flash every time you type a new character. Could you please check what went wrong? Thanks – Legolas Wang Mar 29 '20 at 00:27
  • 1
    Works perfectly on macOS. By far the cleanest answer so far... – vfxdev Apr 10 '20 at 13:48
  • 4
    I couldn't get this to work on Introspect 0.0.6 nor 0.1.0. :( – Jeremy Oct 30 '20 at 20:10
  • This works for me, but only with one field. If the view is refreshed, it will focus again. It can be overcome with setting a state variable firstTime or something, so it will not come again. – Burgler-dev Feb 16 '21 at 14:30
  • Works fine for macOS and iOS. – Nick N May 27 '21 at 11:50
  • I'm trying to use this (0.1.3) inside a [PartialSheet](https://github.com/AndreaMiotto/PartialSheet), and it's not working for me on 14.7.1 device, regardless of what ordering I use for the modifiers. – Rick Jul 30 '21 at 09:10
  • It works only with calling becomeFirstResponder in DispatchQueue.main.async with after 1 sec. – Yusuf Jan 10 '22 at 11:52
  • In my use case, the .focused doesn't work, and this does – 闪电狮 Jan 31 '22 at 10:44
  • This was the only solution that worked for me. – cdignam Feb 18 '22 at 22:36
  • Works for me with Introspect 0.1.4, in Xcode 13/iOS 15.5, on a TextField. If this doesn't work for you, and your use case is to focus a text field when a view appears, consider the `asyncAfter` hack from this thread as an alternative: https://developer.apple.com/forums/thread/681962. – nishanthshanmugham Jun 04 '22 at 15:57
99

Swift UI 3

As of Xcode 13, you can use the focused modifier to make a view become first responder.


Swift UI 1/2

It doesn't seem to be possible at the moment, but you can implement something similar yourself.

You can create a custom text field and add a value to make it become first responder.

struct CustomTextField: UIViewRepresentable {

    class Coordinator: NSObject, UITextFieldDelegate {

        @Binding var text: String
        var didBecomeFirstResponder = false

        init(text: Binding<String>) {
            _text = text
        }

        func textFieldDidChangeSelection(_ textField: UITextField) {
            text = textField.text ?? ""
        }

    }

    @Binding var text: String
    var isFirstResponder: Bool = false

    func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
        let textField = UITextField(frame: .zero)
        textField.delegate = context.coordinator
        return textField
    }

    func makeCoordinator() -> CustomTextField.Coordinator {
        return Coordinator(text: $text)
    }

    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
        uiView.text = text
        if isFirstResponder && !context.coordinator.didBecomeFirstResponder  {
            uiView.becomeFirstResponder()
            context.coordinator.didBecomeFirstResponder = true
        }
    }
}

Note: didBecomeFirstResponder is needed to make sure the text field becomes first responder only once, not on every refresh by SwiftUI!

You would use it like this...

struct ContentView : View {

    @State var text: String = ""

    var body: some View {
        CustomTextField(text: $text, isFirstResponder: true)
            .frame(width: 300, height: 50)
            .background(Color.red)
    }

}

P.S. I added a frame as it doesn't behave like the stock TextField, meaning there's more stuff going on behind the scenes.

More on Coordinators in this excellent WWDC 19 talk: Integrating SwiftUI

Tested on Xcode 11.4

Matteo Pacini
  • 21,796
  • 7
  • 67
  • 74
  • 1
    @MatteoPacini this doesn't seem to work on Mac, do you know if there's any solution that works? – kipelovets Oct 09 '19 at 19:44
  • @kipelovets it works when you click any element inside your active view first before launching a modal containing this custom textfield. – stardust4891 Nov 29 '19 at 05:24
  • I just ran into this issue where calling `textfield.becomeFirstResponder()` inside of `override func layoutSubviews` caused my app to enter an infinite loop. This happened when I was trying to push on a new ViewController that also had a textView trying to becomFirstResponder inside layoutSubviews. Definitely a sneaky bug to be aware of! – b.lyte Feb 14 '20 at 18:18
  • @matteo What about if you wanted to have the focus/keyboard tab to the next textfield on the return key press on the keyboard. See the question: https://stackoverflow.com/questions/60693733/tab-to-next-text-field-when-next-return-key-hit-on-keyboard-in-swiftui – Learn2Code Mar 15 '20 at 14:41
  • 1
    @Matteo is this still needed? Wondering if there is SwiftUI support for programmatically setting focus to a particular TextField yet? – Greg Apr 19 '20 at 11:29
  • 3
    @Greg Yes, still needed. – roman Apr 20 '20 at 16:44
  • @kipelovets in my case I'm doing an IOS app – Greg Apr 21 '20 at 21:30
  • Can also confirm this crashes on copy-paste use in a SwiftUI app on Xcode 11.4 – Zack Shapiro May 03 '20 at 14:48
  • @ZackShapiro could you please post some test code to look at? I tried copy-pasting on 11.4 and it doesn't crash. – Matteo Pacini May 04 '20 at 01:45
  • I tried this and got an "Unable to simultaneously satisfy constraints." runtime error. – mherzl Aug 07 '20 at 19:41
  • Seems like adding `.scaleToFit()` resolves the problem with the frame in some cases. No need to specify height and width by yourself. `.scaleToFit()` makes it behave as a `TextField` would. For me it works if the `CustomTextField` is itself wrapped inside another `View` – ramzesenok Sep 20 '20 at 01:59
  • @Matteo Pacini, I am trying to use your answer in my code, but I need to call onCommit: {...} for my CustomTextField. How would you handle that? – Michel Sep 28 '20 at 09:43
  • Adding `.fixedSize(horizontal: false, vertical: true)` helped me to make it look like native TextField. – Murlakatam Dec 01 '20 at 17:25
  • I assume `_text` (in the Coordinator initializer) should be `self.text`, the @Binding? – alekop Mar 03 '22 at 00:23
  • 1
    You should add that `focused` only works on iOS 15.0+ .... – Big_Chair May 07 '22 at 09:42
  • This one is crashing on iOS 13. You need to check if uiView.windows is not nil – Scvairy May 24 '22 at 12:49
85

iOS 15

There is a new wrapper called @FocusState that controls the state of the keyboard and the focused keyboard ('aka' firstResponder).

⚠️ Note that if you want to make it focused at the initial time, you MUST apply a delay. It's a known bug of the SwiftUI.

Become First Responder ( Focused )

If you use a focused modifier on the text fields, you can make them become focused:

Demo

Resign first responder ( Dismiss keyboard )

or dismiss the keyboard by setting the variable to nil:

Demo


iOS 13 and above: Old but working!

Simple wrapper struct - Works like a native:

Note that Text binding support added as requested in the comments

struct LegacyTextField: UIViewRepresentable {
    @Binding public var isFirstResponder: Bool
    @Binding public var text: String

    public var configuration = { (view: UITextField) in }

    public init(text: Binding<String>, isFirstResponder: Binding<Bool>, configuration: @escaping (UITextField) -> () = { _ in }) {
        self.configuration = configuration
        self._text = text
        self._isFirstResponder = isFirstResponder
    }

    public func makeUIView(context: Context) -> UITextField {
        let view = UITextField()
        view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        view.addTarget(context.coordinator, action: #selector(Coordinator.textViewDidChange), for: .editingChanged)
        view.delegate = context.coordinator
        return view
    }

    public func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.text = text
        configuration(uiView)
        switch isFirstResponder {
        case true: uiView.becomeFirstResponder()
        case false: uiView.resignFirstResponder()
        }
    }

    public func makeCoordinator() -> Coordinator {
        Coordinator($text, isFirstResponder: $isFirstResponder)
    }

    public class Coordinator: NSObject, UITextFieldDelegate {
        var text: Binding<String>
        var isFirstResponder: Binding<Bool>

        init(_ text: Binding<String>, isFirstResponder: Binding<Bool>) {
            self.text = text
            self.isFirstResponder = isFirstResponder
        }

        @objc public func textViewDidChange(_ textField: UITextField) {
            self.text.wrappedValue = textField.text ?? ""
        }

        public func textFieldDidBeginEditing(_ textField: UITextField) {
            self.isFirstResponder.wrappedValue = true
        }

        public func textFieldDidEndEditing(_ textField: UITextField) {
            self.isFirstResponder.wrappedValue = false
        }
    }
}

Usage:

struct ContentView: View {
    @State var text = ""
    @State var isFirstResponder = false

    var body: some View {
        LegacyTextField(text: $text, isFirstResponder: $isFirstResponder)
    }
}

Bonus: Completely customizable

LegacyTextField(text: $text, isFirstResponder: $isFirstResponder) {
    $0.textColor = .red
    $0.tintColor = .blue
}

This method is fully adaptable. For example, you can see How to add an Activity indicator in SwiftUI with the same method here

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
  • 1
    'ResponderTextField.Type' is not convertible to '(Bool, escaping (ResponderTextField.TheUIView) -> ()) -> ResponderTextField' (aka '(Bool, escaping (UITextField) -> ()) -> ResponderTextField') – stardust4891 Nov 30 '19 at 04:37
  • Where did you get that? – Mojtaba Hosseini Nov 30 '19 at 06:01
  • 4
    This is missing the $text binding (and label) that TextField usually has? (I couldn't get it to work, anyway) – drtofu May 07 '20 at 20:47
  • Where is the textfield? – Peter Schorn Jun 01 '20 at 00:12
  • 1
    @drtofu I've added binding text support. – Mojtaba Hosseini Oct 11 '20 at 13:38
  • 1
    @MojtabaHosseini There are two problems with this. 1. If you write a long sentence, the whole views moves to the left chracter by character as you type in. 2. Configuration does not work. TextColor is stil black. – mahan Nov 12 '20 at 16:43
  • 1
    @mahan for configuration part. Add `configuration(uiView)` in func updateUIView(_ uiView:context:) – Yi-Hsiu Lee Apr 04 '21 at 16:09
  • I really love this solution because it's both versatile and educational for ppl with SwiftUI background. Just one question: What does this do "view.addTarget(context.coordinator, action: #selector(Coordinator.textViewDidChange), for: .editingChanged)"? – lmunck May 23 '21 at 07:32
  • 1
    This is by far the best answer IMO, and ensures the call site is clean and understandable, it's also closest to Apple's solution in iOS 15. – Camsoft Aug 06 '21 at 09:42
  • This doesn't render properly when displayed inside a `Form` element, it loses the leading offset. – Rob Keniger Aug 26 '21 at 21:28
  • The problem with .focused() is that it cannot bind to values in an ObservableObject (eg a view model), FocusState is only allowed in a view. So for UI where you have lots of interacting views and logic split out into a view model, you then need to somehow bind your view model to the FocusState. – Pig Dog Bay Sep 23 '21 at 10:36
  • @mahan I updated the answer by adding `view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)`. It fixes the width expansion. – Alexander Volkov Nov 21 '21 at 17:18
  • I think binding is not working sometimes in my Xocde 13.3 and simulator 15.6 – Kudos Sep 19 '22 at 08:24
  • If anybody see UI improper just add ` DispatchQueue.main.asyncAfter(deadline: .now()+0.05) {/code/}` for `updateUIView` func – Kudos Sep 19 '22 at 08:45
  • still Binding not working after adding DispatchQueue – Muhammad Awais Oct 05 '22 at 08:02
35

iOS 15.0+

macOS 12.0+,

Mac Catalyst 15.0+,

tvOS 15.0+,

watchOS 8.0+

Use focused(_:) if you have a single TextField.

focused(_:)

Modifies this view by binding its focus state to the given Boolean state value.

struct NameForm: View {
    
    @FocusState private var isFocused: Bool
    
    @State private var name = ""
    
    var body: some View {
        TextField("Name", text: $name)
            .focused($isFocused)
        
        Button("Submit") {
            if name.isEmpty {
                isFocued = true
            }
        }
    }
}

Use focused(_:equals:) should you have multiple TextFields.

focused(_:equals:)

Modifies this view by binding its focus state to the given state value.

struct LoginForm: View {
    enum Field: Hashable {
        case usernameField
        case passwordField
    }

    @State private var username = ""
    @State private var password = ""
    @FocusState private var focusedField: Field?

    var body: some View {
        Form {
            TextField("Username", text: $username)
                .focused($focusedField, equals: .usernameField)

            SecureField("Password", text: $password)
                .focused($focusedField, equals: .passwordField)

            Button("Sign In") {
                if username.isEmpty {
                    focusedField = .usernameField
                } else if password.isEmpty {
                    focusedField = .passwordField
                } else {
                    handleLogin(username, password)
                }
            }
        }
    }
}

SwiftUI Documentation


Update

I tested this in Xcode version 13.0 beta 5 (13A5212g). It works

mokagio
  • 16,391
  • 3
  • 51
  • 58
mahan
  • 12,366
  • 5
  • 48
  • 83
25

For anyone who ended up here but faced crashed using @Matteo Pacini's answer, please be aware of this change in beta 4: Cannot assign to property: '$text' is immutable about this block:

init(text: Binding<String>) {
    $text = text
}

should use:

init(text: Binding<String>) {
    _text = text
}

And if you want to make the textfield become first responder in a sheet, please be aware that you cannot call becomeFirstResponder until the textfield is shown. In other words, putting @Matteo Pacini's textfield directly in sheet content causes crash.

To solve the issue, add an additional check uiView.window != nil for textfield's visibility. Only focus after it is in the view hierarchy:

struct AutoFocusTextField: UIViewRepresentable {
    @Binding var text: String

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

    func makeUIView(context: UIViewRepresentableContext<AutoFocusTextField>) -> UITextField {
        let textField = UITextField()
        textField.delegate = context.coordinator
        return textField
    }

    func updateUIView(_ uiView: UITextField, context:
        UIViewRepresentableContext<AutoFocusTextField>) {
        uiView.text = text
        if uiView.window != nil, !uiView.isFirstResponder {
            uiView.becomeFirstResponder()
        }
    }

    class Coordinator: NSObject, UITextFieldDelegate {
        var parent: AutoFocusTextField

        init(_ autoFocusTextField: AutoFocusTextField) {
            self.parent = autoFocusTextField
        }

        func textFieldDidChangeSelection(_ textField: UITextField) {
            parent.text = textField.text ?? ""
        }
    }
}
Rui Ying
  • 809
  • 8
  • 12
  • I was stuck because I was using the `shouldChangeCharactersIn` UITextField delegate while also updating the text in `updateUIView`. By changing to `textFieldDidChangeSelection` to capture the text in the binding, the custom control works great! Thanks. – P. Ent Dec 11 '19 at 11:18
  • 1
    It does indeed crash in `sheet` when trying to focus before the input is show. You solution does not crash but it does not focus either... At least in my tests. Basically, updateUIView is called only once. – NeverwinterMoon Apr 13 '20 at 12:49
  • This behaves strange if the parent view of the UITextfield is in a middle of an animation – Marwan Roushdy Apr 21 '20 at 01:10
  • This crashes for me if another textfield had focus when the sheet with the `AutoFocusTextField` was presented. I had to wrap the call to `becomeFirstResponder()` in a `DispatchQueue.main.async` block. – Brad May 13 '20 at 20:59
  • Wrapping in `DispatchQueue.main.async` is definitely not working out for me with transition animation. Say, using NavigationView and going from view 1 to view 2 (where becomeFirstResponder is called), with async, the transition animation is played twice. – NeverwinterMoon May 23 '20 at 11:55
  • Is there a macOS version of this? – Peter Schorn Jun 01 '20 at 00:59
12

ResponderChain

I made this small package for cross-platform first responder handling without subclassing views or making custom ViewRepresentables in SwiftUI on iOS 13+

https://github.com/Amzd/ResponderChain

How to apply it to your problem

SceneDelegate.swift

...
// Set the ResponderChain as environmentObject
let rootView = ContentView().environmentObject(ResponderChain(forWindow: window))
...

ContentView.swift

struct ContentView: View {

    @EnvironmentObject var chain: ResponderChain
    @State var showingTextField = false
    @State var text = ""

    var body: some View {
        return VStack {
            if showingTextField {
                TextField($text).responderTag("field1").onAppear {
                    DispatchQueue.main.async {
                        chain.firstResponder = "field1"
                    }
                }
            }
            Button(action: { self.showingTextField.toggle() }) {
                Text ("Show")
            }
        }
    }
}
Casper Zandbergen
  • 3,419
  • 2
  • 25
  • 49
  • 2
    Most elegant solution I have seen so far. Great support on SwiftUI. Used myself and highly recommend! – Legolas Wang Jan 04 '21 at 22:12
  • 5
    You should mention that this solution includes dependencies on two other packages. – Gene Loparco Jan 08 '21 at 06:03
  • @GeneLoparco well one of them is Introspect which I would recommend to use in any SwiftUI app anyway and the other is a single file with 50 lines. – Casper Zandbergen Jan 10 '21 at 01:39
  • 3
    I understand that, but it may be helpful to others to know that your solution is not standalone and that there are other code dependencies. This does not mean that the solution is good or bad. It is just useful to know about dependencies, that's all. – Gene Loparco Jan 12 '21 at 16:04
  • This seems to work nicely except that when I use it on a modal, when I dismiss the modal it crashes with 'Fatal error: No ObservableObject of type ResponderChain found. A View.environmentObject(_:) for ResponderChain may be missing as an ancestor of this view.' – SomaMan Jan 20 '21 at 17:09
  • 1
    @SomaMan well that’s because you have to attach environmentObjects to modals again. More detailed explanation: https://medium.com/swlh/swiftui-and-the-missing-environment-object-1a4bf8913ba7 – Casper Zandbergen Jan 20 '21 at 19:21
  • @CasperZandbergen thanks for that, it works now - I'm pretty new to SwiftUI & there's a lot of stuff which is not particularly intuitive... – SomaMan Jan 21 '21 at 11:57
  • @SomaMan yeah they made environment objects even less intuitive in Xcode 12 sadly – Casper Zandbergen Jan 21 '21 at 11:58
  • @CasperZandbergen yeah, coming from far too many years of imperative-style programming, I sadly don't find much of SwiftUI intuitive – SomaMan Jan 21 '21 at 12:01
  • @CasperZandbergen Is there a complete sample project that shows how to use ResponderChain? I can't even figure out how to initialize it given my project has no SceneDelegate or ApplicationDelegate. – SafeFastExpressive Jan 25 '21 at 00:48
  • @SafeFastExpressive the readme has a pretty extensive explanation – Casper Zandbergen Jan 25 '21 at 07:02
  • 2
    @CasperZandbergen Its very nice but doesn't answer my initialization question. A small sample app that demonstrates it in the latest SwiftUI release would go a long way for new SwiftUI developers like myself. I'm entirely unfamiliar with what SceneDelegate & ApplicationDelegate are since they no longer exist in Apples latest example code. I realize SwiftUI itself is terribly documented, but I think that just makes it even more necessary for libraries to document their usage and assumptions extensively. – SafeFastExpressive Jan 25 '21 at 23:28
  • Always returns EmptyView. – Lars Jul 02 '21 at 19:45
  • @Lars please open an issue on the github with a bit more info – Casper Zandbergen Jul 03 '21 at 11:59
  • 1
    Since this solution depends internally on Introspect anyway, for basic use, the [one-line solution](https://stackoverflow.com/a/59277051/145173) using Introspect directly is a leaner choice. – Edward Brey Jul 22 '21 at 17:53
  • @EdwardBrey that solution calls becomeFirstResponder every time SwiftUI recalculates the UI (for example when an ObservedObject or State variable changes in a parent view) so you will get the textfield focusing when you wouldn't expect it – Casper Zandbergen Sep 23 '21 at 07:26
10

In my case I wanted to focused a textfield just right away I used .onappear function

struct MyView: View {
    
    @FocusState private var isTitleTextFieldFocused: Bool

    @State private var title = ""
    
    var body: some View {
        VStack {
            TextField("Title", text: $title)
                .focused($isTitleTextFieldFocused)
            
        }
        .padding()
        .onAppear {
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                self.isTitleTextFieldFocused = true
            }
            
        }
    }
}
Dasoga
  • 5,489
  • 4
  • 33
  • 40
7

As others have noted (e.g. @kipelovets comment on the accepted answer, e.g. @Eonil's answer), I have also not found any of the accepted solutions to work on macOS. I've had some luck, however, using NSViewControllerRepresentable to get a NSSearchField to appear as the first responder in a SwiftUI view:

import Cocoa
import SwiftUI

class FirstResponderNSSearchFieldController: NSViewController {

  @Binding var text: String

  init(text: Binding<String>) {
    self._text = text
    super.init(nibName: nil, bundle: nil)
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func loadView() {
    let searchField = NSSearchField()
    searchField.delegate = self
    self.view = searchField
  }

  override func viewDidAppear() {
    self.view.window?.makeFirstResponder(self.view)
  }
}

extension FirstResponderNSSearchFieldController: NSSearchFieldDelegate {

  func controlTextDidChange(_ obj: Notification) {
    if let textField = obj.object as? NSTextField {
      self.text = textField.stringValue
    }
  }
}

struct FirstResponderNSSearchFieldRepresentable: NSViewControllerRepresentable {

  @Binding var text: String

  func makeNSViewController(
    context: NSViewControllerRepresentableContext<FirstResponderNSSearchFieldRepresentable>
  ) -> FirstResponderNSSearchFieldController {
    return FirstResponderNSSearchFieldController(text: $text)
  }

  func updateNSViewController(
    _ nsViewController: FirstResponderNSSearchFieldController,
    context: NSViewControllerRepresentableContext<FirstResponderNSSearchFieldRepresentable>
  ) {
  }
}

Sample SwiftUI view:

struct ContentView: View {

  @State private var text: String = ""

  var body: some View {
    FirstResponderNSSearchFieldRepresentable(text: $text)
  }
}

David Muller
  • 201
  • 3
  • 5
5

To fill in this missing functionality, you may install SwiftUIX using Swift Package Manager:

  1. In Xcode, open your project and navigate to File → Swift Packages → Add Package Dependency...
  2. Paste the repository URL (https://github.com/SwiftUIX/SwiftUIX) and click Next.
  3. For Rules, select Branch (with branch set to master).
  4. Click Finish.
  5. Open the Project settings, add SwiftUI.framework to the Linked Frameworks and Libraries, set Status to Optional.

More Info: https://github.com/SwiftUIX/SwiftUIX

import SwiftUI
import SwiftUIX

struct ContentView : View {

    @State var showingTextField = false
    @State var text = ""

    var body: some View {
        return VStack {
            if showingTextField {
                CocoaTextField("Placeholder text", text: $text)
                    .isFirstResponder(true)
                    .frame(width: 300, height: 48, alignment: .center)
            }
            Button(action: { self.showingTextField.toggle() }) {
                Text ("Show")
            }
        }
    }
}
Joanna J.
  • 106
  • 1
  • 3
5

SwiftUI

struct ContentView: View {
enum Field {
    case firstTextfield
    case secondTextfield
    case lastTextfield
}

@State private var firstTextfield = ""
@State private var secondTextfield = ""
@State private var lastTextfield = ""
@FocusState private var focusedField: Field?

var body: some View {
    VStack {
        TextField("Enter anything on first textfield", text: $firstTextfield)
            .focused($focusedField, equals: .firstTextfield)
            .submitLabel(.next)

        TextField("Enter anything on second textfield", text: $secondTextfield)
            .focused($focusedField, equals: .secondTextfield)
            .submitLabel(.next)

        TextField("Enter anything on last textfield", text: $lastTextfield)
            .focused($focusedField, equals: .lastTextfield)
            .submitLabel(.join)
    }
    .onSubmit {
        switch focusedField {
        case .firstTextfield:
            focusedField = .secondTextfield
        case .secondTextfield:
            focusedField = .lastTextfield
        default:
            focusedField = nil
        }
    }
  }
}

Description: Add an enum with textfields cases, and a property wrapped in a @FocusState with type of that enum. Add focused(_:equals:) modifier to have a binding value, equal to the enum cases. Now, you can change the focusedField to whichever textfield you want to have cursor on, or resign first responder by assigning nil to focusedField.

Aashish
  • 2,532
  • 2
  • 23
  • 28
3

Not really an answer, just building on Casper's great solution with a convenient modifier -

struct StartInput: ViewModifier {
    
    @EnvironmentObject var chain: ResponderChain
    
    private let tag: String
    
    
    init(tag: String) {
        self.tag = tag
    }
    
    func body(content: Content) -> some View {
        
        content.responderTag(tag).onAppear() {
            DispatchQueue.main.async {
                chain.firstResponder = tag
            }
        }
    }
}


extension TextField {
    
    func startInput(_ tag: String = "field") -> ModifiedContent<TextField<Label>, StartInput> {
        self.modifier(StartInput(tag: tag))
    }
}

Just use like -

TextField("Enter value:", text: $quantity)
    .startInput()
SomaMan
  • 4,127
  • 1
  • 34
  • 45
  • 2
    I would like to upvote this given how clean a solution your modifier is, but needs to document it's dependencies. First, what is Async? Second, if ResponderChain and Introspect are required, show them as imports, and provide their URLs for Swift Package manager. This is already a popular question, Caspers answer may be hard to find after more answers. – SafeFastExpressive Jan 25 '21 at 00:16
  • 1
    @SafeFastExpressive apologies for the Async.main - that was just a wrapper around DispatchQueue.async.main which I forgot to remove. Re your second point, I don't feel it's my place to point out dependencies of someone else's answer to which I'm referring :) – SomaMan Jan 26 '21 at 08:55
  • You are right. This really isn't an answer - and neither is your comment. I can't figure out how to make this "elegant" answer work without more information. – FontFamily Jun 05 '21 at 03:39
2

This is a ViewModifier that works with introspect. It works for AppKit MacOS, Xcode 11.5

    struct SetFirstResponderTextField: ViewModifier {
      @State var isFirstResponderSet = false

      func body(content: Content) -> some View {
         content
            .introspectTextField { textField in
            if self.isFirstResponderSet == false {
               textField.becomeFirstResponder()
               self.isFirstResponderSet = true
            }
         }
      }
   }
Gregor Brandt
  • 7,659
  • 38
  • 59
2

We have a solution that makes controlling the first responder effortless.

https://github.com/mobilinked/MbSwiftUIFirstResponder

TextField("Name", text: $name)
    .firstResponder(id: FirstResponders.name, firstResponder: $firstResponder, resignableUserOperations: .all)

TextEditor(text: $notes)
    .firstResponder(id: FirstResponders.notes, firstResponder: $firstResponder, resignableUserOperations: .all)
swift code
  • 115
  • 1
  • 1
1

Selected answer causes some infinite loop issue with AppKit. I don't know about UIKit case.

To avoid that issue, I recommend just sharing NSTextField instance directly.

import AppKit
import SwiftUI

struct Sample1: NSViewRepresentable {
    var textField: NSTextField
    func makeNSView(context:NSViewRepresentableContext<Sample1>) -> NSView { textField }
    func updateNSView(_ x:NSView, context:NSViewRepresentableContext<Sample1>) {}
}

You can use that like this.

let win = NSWindow()
let txt = NSTextField()
win.setIsVisible(true)
win.setContentSize(NSSize(width: 256, height: 256))
win.center()
win.contentView = NSHostingView(rootView: Sample1(textField: txt))
win.makeFirstResponder(txt)

let app = NSApplication.shared
app.setActivationPolicy(.regular)
app.run()

This breaks pure value semantic, but depending on AppKit means you partially abandon pure value semantic and gonna afford some dirtiness. This is a magic hole we need right now for deal with lack of first-responder control in SwiftUI.

As we access NSTextField directly, setting first-responder is plain AppKit way, therefore no visible source of trouble.

You can download working source code here.

eonil
  • 83,476
  • 81
  • 317
  • 516
1

I took a little bit different approach - instead of UIViewRepresentable based on UITextField i made it based on UIView and and plug it in SwiftUI view hierarchy with background modifier. Inside UIView i added logic to find first view that canBecomeFirstResponder in subviews and parent views.

private struct FocusableUIView: UIViewRepresentable {
    var isFirstResponder: Bool = false

    class Coordinator: NSObject {
        var didBecomeFirstResponder = false
    }

    func makeUIView(context: UIViewRepresentableContext<FocusableUIView>) -> UIView {
        let view = UIView()
        view.backgroundColor = .clear
        return view
    }

    func makeCoordinator() -> FocusableUIView.Coordinator {
        return Coordinator()
    }

    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<FocusableUIView>) {
        guard uiView.window != nil, isFirstResponder, !context.coordinator.didBecomeFirstResponder else {
            return
        }

        var foundRepsonder: UIView?
        var currentSuperview: UIView? = uiView
        repeat {
            foundRepsonder = currentSuperview?.subviewFirstPossibleResponder
            currentSuperview = currentSuperview?.superview
        } while foundRepsonder == nil && currentSuperview != nil

        guard let responder = foundRepsonder else {
            return
        }

        DispatchQueue.main.async {
            responder.becomeFirstResponder()
            context.coordinator.didBecomeFirstResponder = true
        }
    }
}

private extension UIView {
    var subviewFirstPossibleResponder: UIView? {
        guard !canBecomeFirstResponder else { return self }

        for subview in subviews {
            if let firstResponder = subview.subviewFirstPossibleResponder {
                return firstResponder
            }
        }

        return nil
    }
}


Here is an example how to use it to make TextField to autofocus (+ bonus utilise @FocusState new iOS 15 api).

extension View {
    @ViewBuilder
    func autofocus() -> some View {
        if #available(iOS 15, *) {
            modifier(AutofocusedViewModifiers.Modern())
        } else {
            modifier(AutofocusedViewModifiers.Legacy())
        }
    }
}

private enum AutofocusedViewModifiers {
    struct Legacy: ViewModifier {
        func body(content: Content) -> some View {
            content
                .background(FocusableUIView(isFirstResponder: isFocused))
                .onAppear {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                        isFocused = true
                    }
                }
        }

        @State private var isFocused = false
    }

    @available(iOS 15, *)
    struct Modern: ViewModifier {
        func body(content: Content) -> some View {
            content
                .focused($isFocused)
                .onAppear {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                        isFocused = true
                    }
                }
        }

        @FocusState private var isFocused: Bool
    }
}

Content view example:

struct ContentView: View {
    @State private var text = ""

    var body: some View {
        VStack {
            TextField("placeholder", text: $text)
            Text("some text")
        }
        .autofocus()
    }
}

alex1704
  • 479
  • 5
  • 8
0

Since Responder Chain is not available to be consumed via SwiftUI, so we have to consume it using UIViewRepresentable.

Do have a look at the below link as I have made a workaround that can work similarly to the way we use to do using UIKit.

https://stackoverflow.com/a/61121199/6445871

Anshuman Singh
  • 1,018
  • 15
  • 17
0

It is my variant of the implementation, based on @Mojtaba Hosseini and @Matteo Pacini solutions. I am still new to SwiftUI, so I won't guarantee the absolute correctness of the code, but it works.

I hope it would be helpful to someone.

ResponderView: It is a generic-responder view, that could be used with any UIKit view.

struct ResponderView<View: UIView>: UIViewRepresentable {
    @Binding var isFirstResponder: Bool
    var configuration = { (view: View) in }

    func makeUIView(context: UIViewRepresentableContext<Self>) -> View { View() }

    func makeCoordinator() -> Coordinator {
        Coordinator($isFirstResponder)
    }

    func updateUIView(_ uiView: View, context: UIViewRepresentableContext<Self>) {
        context.coordinator.view = uiView
        _ = isFirstResponder ? uiView.becomeFirstResponder() : uiView.resignFirstResponder()
        configuration(uiView)
    }
}

// MARK: - Coordinator
extension ResponderView {
    final class Coordinator {
        @Binding private var isFirstResponder: Bool
        private var anyCancellable: AnyCancellable?
        fileprivate weak var view: UIView?

        init(_ isFirstResponder: Binding<Bool>) {
            _isFirstResponder = isFirstResponder
            self.anyCancellable = Publishers.keyboardHeight.sink(receiveValue: { [weak self] keyboardHeight in
                guard let view = self?.view else { return }
                DispatchQueue.main.async { self?.isFirstResponder = view.isFirstResponder }
            })
        }
    }
}

// MARK: - keyboardHeight
extension Publishers {
    static var keyboardHeight: AnyPublisher<CGFloat, Never> {
        let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification)
            .map { ($0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0 }

        let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification)
            .map { _ in CGFloat(0) }

        return MergeMany(willShow, willHide)
            .eraseToAnyPublisher()
    }
}

struct ResponderView_Previews: PreviewProvider {
    static var previews: some View {
        ResponderView<UITextField>.init(isFirstResponder: .constant(false)) {
            $0.placeholder = "Placeholder"
        }.previewLayout(.fixed(width: 300, height: 40))
    }
}

ResponderTextField - It is a convenient text-field wrapper around ResponderView.

struct ResponderTextField: View {
    var placeholder: String
    @Binding var text: String
    @Binding var isFirstResponder: Bool
    private var textFieldDelegate: TextFieldDelegate

    init(_ placeholder: String, text: Binding<String>, isFirstResponder: Binding<Bool>) {
        self.placeholder = placeholder
        self._text = text
        self._isFirstResponder = isFirstResponder
        self.textFieldDelegate = .init(text: text)
    }

    var body: some View {
        ResponderView<UITextField>(isFirstResponder: $isFirstResponder) {
            $0.text = self.text
            $0.placeholder = self.placeholder
            $0.delegate = self.textFieldDelegate
        }
    }
}

// MARK: - TextFieldDelegate
private extension ResponderTextField {
    final class TextFieldDelegate: NSObject, UITextFieldDelegate {
        @Binding private(set) var text: String

        init(text: Binding<String>) {
            _text = text
        }

        func textFieldDidChangeSelection(_ textField: UITextField) {
            text = textField.text ?? ""
        }
    }
}

struct ResponderTextField_Previews: PreviewProvider {
    static var previews: some View {
        ResponderTextField("Placeholder",
                           text: .constant(""),
                           isFirstResponder: .constant(false))
            .previewLayout(.fixed(width: 300, height: 40))
    }
}

And way to use that.

struct SomeView: View {
    @State private var login: String = ""
    @State private var password: String = ""
    @State private var isLoginFocused = false
    @State private var isPasswordFocused = false

    var body: some View {
        VStack {
            ResponderTextField("Login", text: $login, isFirstResponder: $isLoginFocused)
            ResponderTextField("Password", text: $password, isFirstResponder: $isPasswordFocused)
        }
    }
}
Dmytro Shvetsov
  • 946
  • 10
  • 13
0

Expanding on @JoshuaKifer answer's above, if you're dealing with the navigation animation being glitchy when using Introspect to make a text field first responder. Use this:

import SchafKit

@State var field: UITextField?

TextField("", text: $value)
.introspectTextField { textField in
    field = textField
}
.onDidAppear {
     field?.becomeFirstResponder()
}

More details on this solution here.

Ole Pannier
  • 3,208
  • 9
  • 22
  • 33
0

The correct SwiftUI way is to use @FocusState as mentioned above. However this API is available only for iOS 15. If you are using iOS 14 or iOS 13 you can use the Focuser library which is modelled to follow Apple API.

https://github.com/art-technologies/swift-focuser

enter image description here

Here's an example code. You will notice that API looks almost exactly as Apple, however Focuser also offers to use keyboard to move first responder down the chain which is pretty handy.

enter image description here

Markon
  • 741
  • 8
  • 23
0

If you're having any problem with @JoshuaKifer or @ahaze 's response, I've solved mine by using the modifier on the parent class, instead of on the TextField itself.

What I was doing:

TextField("Placeholder text...", text: $searchInput)
    .introspectTextField { textField in
        textField.becomeFirstResponder()
    }

How I solved my problem:

   YourParentStruct(searchInput: $searchInput)
       .introspectTextField { textField in
           textField.becomeFirstResponder()
       }

I'll put the definition of the parent struct below just for clearness

   struct YourParentStruct: View {
       @Binding var searchInput: String

       var body: some View {
           HStack {
               TextField("Placeholder text...", text: $searchInput)                      
                  .padding()
                  .background(Color.gray)
                  .cornerRadius(10)
           }
       }
   }
0

At its Simplest form in iOS 13 ( without using any third party sdk/repo , or if you havent upgraded to iOS 14 to utilise the focus modifiers)

struct CustomTextField: UIViewRepresentable {
            
    func makeUIView(context: Context) -> UITextField {
         UITextField(frame: .zero)
    }
    
    func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.becomeFirstResponder()
    }
}

Usage:

struct ContentView : View {

    var body: some View {
        CustomTextField()
            .frame(width: 300, height: 50)
            .background(Color.red)
    }
}
Naishta
  • 11,885
  • 4
  • 72
  • 54
0

I know its too late but if it helps anyone this is how I do it.

import SwiftUI
import Introspect

struct MyView: View {
    
    @Binding var text1: String
    @Binding var text2: String
    
    @State private var toggleTF: Bool = false

    var body: some View {
        
        TextField("TextField 1", text: $text1)
            .introspectTextField{ tF in
                if toggleTF {
                    tF.becomeFirstResponder()
                }
            }
        
        TextField("TextField 2", text: $text1)
            .introspectTextField{ tF in
                if !toggleTF {
                    tF.becomeFirstResponder()
                }
            }
        
        Button("Toggle textfields") {
            toggleTF.toggle()
        }
    }
} 
vigu
  • 21
  • 4