SwiftUI: How to make TextField become first responder? - swift

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).

Using SwiftUI-Introspect, you can do:
TextField("", text: $value)
.introspectTextField { textField in
textField.becomeFirstResponder()
}

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

iOS 15
There is a new wrapper called #FocusState that controls the state of the keyboard and the focused keyboard ('aka' firstResponder).
Become First Responder ( Focused )
If you use a focused modifier on the text fields, you can make them become focused:
Resign first responder ( Dismiss keyboard )
or dismiss the keyboard by setting the variable to nil:
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

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
focused(_:)
focused(_:equals:)
#FocusState
Update
I tested this in Xcode version 13.0 beta 5 (13A5212g). It works

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 ?? ""
}
}
}

📦 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")
}
}
}
}

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
}
}
}
}

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)
}
}

To fill in this missing functionality, you may install SwiftUIX using Swift Package Manager:
In Xcode, open your project and navigate to File → Swift Packages → Add Package Dependency...
Paste the repository URL (https://github.com/SwiftUIX/SwiftUIX) and click Next.
For Rules, select Branch (with branch set to master).
Click Finish.
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")
}
}
}
}

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.

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()

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
}
}
}
}

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)

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.

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()
}
}

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

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)
}
}
}

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.

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
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.

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)
}
}
}

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)
}
}

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()
}
}
}

As SwiftUI 2 doesn't support first responders yet I use this solution. It is dirty, but might work for some use cases when you only have 1 UITextField and 1 UIWindow.
import SwiftUI
struct MyView: View {
#Binding var text: String
var body: some View {
TextField("Hello world", text: $text)
.onAppear {
UIApplication.shared.windows.first?.rootViewController?.view.textField?.becomeFirstResponder()
}
}
}
private extension UIView {
var textField: UITextField? {
subviews.compactMap { $0 as? UITextField }.first ??
subviews.compactMap { $0.textField }.first
}
}

Related

Is there a way to show the system keyboard and take inputs from it without a SwiftUI TextField?

I want to be able to display the system keyboard and my app take inputs from the keyboard without using a TextField or the like. My simple example app is as follows:
struct TypingGameView: View {
let text = “Some example text”
#State var displayedText: String = ""
var body: some View {
Text(displayedText)
}
}
I'm making a memorization app, so when I user types an input on the keyboard, it should take the next word from text and add it to displayedText to display onscreen. The keyboard should automatically pop up when the view is displayed.
If there is, a native SwiftUI solution would be great, something maybe as follows:
struct TypingGameView: View {
let text = “Some example text”
#State var displayedText: String = ""
var body: some View {
Text(displayedText)
.onAppear {
showKeyboard()
}
.onKeyboardInput { keyPress in
displayedText += keyPress
}
}
}
A TextField could work if there is some way to 1. Make it so that whatever is typed does not display in the TextField, 2. Disable tapping the text (e.g. moving the cursor or selecting), 3. Disable deleting text.
Here's a possible solution using UIViewRepresentable:
Create a subclass of UIView that implements UIKeyInput but doesn't draw anything
Wrap it inside a struct implementing UIViewRepresentable, use a Coordinator as a delegate to your custom UIView to carry the edited text "upstream"
Wrap it again in a ViewModifier that shows the content, pass a binding to the wrapper and triggers the first responder of your custom UIView when tapped
I'm sure there's a more synthetic solution to find, three classes for such a simple problem seems a bit much.
protocol InvisibleTextViewDelegate {
func valueChanged(text: String?)
}
class InvisibleTextView: UIView, UIKeyInput {
var text: String?
var delegate: InvisibleTextViewDelegate?
override var canBecomeFirstResponder: Bool { true }
// MARK: UIKeyInput
var keyboardType: UIKeyboardType = .decimalPad
var hasText: Bool { text != nil }
func insertText(_ text: String) {
self.text = (self.text ?? "") + text
setNeedsDisplay()
delegate?.valueChanged(text: self.text)
}
func deleteBackward() {
if var text = text {
_ = text.popLast()
self.text = text
}
setNeedsDisplay()
delegate?.valueChanged(text: self.text)
}
}
struct InvisibleTextViewWrapper: UIViewRepresentable {
typealias UIViewType = InvisibleTextView
#Binding var text: String?
#Binding var isFirstResponder: Bool
class Coordinator: InvisibleTextViewDelegate {
var parent: InvisibleTextViewWrapper
init(_ parent: InvisibleTextViewWrapper) {
self.parent = parent
}
func valueChanged(text: String?) {
parent.text = text
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> InvisibleTextView {
let view = InvisibleTextView()
view.delegate = context.coordinator
return view
}
func updateUIView(_ uiView: InvisibleTextView, context: Context) {
if isFirstResponder {
uiView.becomeFirstResponder()
} else {
uiView.resignFirstResponder()
}
}
}
struct EditableText: ViewModifier {
#Binding var text: String?
#State var editing: Bool = false
func body(content: Content) -> some View {
content
.background(InvisibleTextViewWrapper(text: $text, isFirstResponder: $editing))
.onTapGesture {
editing.toggle()
}
.background(editing ? Color.gray : Color.clear)
}
}
extension View {
func editableText(_ text: Binding<String?>) -> some View {
modifier(EditableText(text: text))
}
}
struct CustomTextField_Previews: PreviewProvider {
struct Container: View {
#State private var value: String? = nil
var body: some View {
HStack {
if let value = value {
Text(value)
Text("meters")
.font(.subheadline)
} else {
Text("Enter a value...")
}
}
.editableText($value)
}
}
static var previews: some View {
Group {
Container()
}
}
}
you can have the textfield on screen but set opacity to 0 if you don't want it shown. Though this would not solve for preventing of deleting the text
You can then programmatically force it to become first responder using something like this https://stackoverflow.com/a/56508132/3919220

UIViewRepresentable UITextField skips first character when connected to ObservableObject's #Published property

I have a CustomTextField conforming to UIViewRepresentable. This field is connected to the ViewModel's #Published email property. The ViewModel itself conforms to ObservableObject. When I launch the app on iPhone 12 Pro Max (14.3) simulator, whenever I try to type the first character, it doesn't appear in the field nor it gets added the email's value. The consecutive characters are typed just fine.
Here is the code:
import SwiftUI
class ViewModel: ObservableObject {
#Published var email = ""
}
struct ContentView: View {
#ObservedObject var vm = ViewModel()
var body: some View {
ZStack {
Color.blue
CustomTextField(text: $vm.email, placeholder: "Email")
.frame(height: 40)
.padding()
}
.ignoresSafeArea()
}
}
struct CustomTextField: UIViewRepresentable {
#Binding var text: String
var placeholder: String
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.borderStyle = .roundedRect
textField.placeholder = placeholder
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.spellCheckingType = .no
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ view: UITextField, context: Context) {
view.text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(text: $text)
}
class Coordinator: NSObject, UITextFieldDelegate {
#Binding var text: String
init(text: Binding<String>) {
_text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
DispatchQueue.main.async {
self.text = textField.text ?? ""
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Does anyone know what's wrong here?
If you initialize your model in View, you should use #StateObject instead of #ObservedObject.
Also, you may try assigning textField.text = text in the makeUIView method.
And I feel like this approach for Representable objects is more elegant, take a look ^^
https://github.com/twostraws/VisualEffects
The problem was in Xcode. Once I upgraded to 12.4, the issue went away.

How to initialise #State variables depending on each other in Swiftui?

I need to assign my MultilineTextField view (a wrapped UITextView) to a variable textField in order to be able later to call its method updateTextStyle from a button in ContentView (the method takes the selected text and turns it into bold). The problem is that MultilineTextField rely on the #State var range, therefore not compiling. What are possible workaround for this?
struct ContentView: View {
#State private var range: NSRange?
#State var textField = MultilineTextField(rangeSelected: $range)
var body: some View {
VStack {
textField
Button(action: {
self.textField.updateTextStyle()
}) {
Text("Update text style")
}
}
}
}
In case relevant, MultilineTextField (I tried to remove the unnecessary - hope it's clear)
struct MultilineTextField: UIViewRepresentable {
let textView = UITextView()
#Binding var rangeSelected: NSRange?
#State var attributedNoteText = NSMutableAttributedString(string: "Lorem ipsum")
func makeUIView(context: Context) -> UITextView {
// ...
textView.delegate = context.coordinator
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.attributedText = attributedNoteText
}
func updateTextStyle() {
if self.rangeSelected != nil {
// apply attributes (makes the selected text bold)
} else {
print("rangeSelected is nil")
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self, $attributedNoteText)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: MultilineTextField
var text: Binding<NSMutableAttributedString>
init(parent: MultilineTextField, _ text: Binding<NSMutableAttributedString>) {
self.parent = parent
self.text = text
}
func textViewDidChange(_ textView: UITextView) {
let attributedStringCopy = textView.attributedText?.mutableCopy() as! NSMutableAttributedString
parent.textView.attributedText = attributedStringCopy
self.text.wrappedValue = attributedStringCopy
}
func textViewDidChangeSelection(_ textView: UITextView) {
parent.rangeSelected = textView.selectedRange // not sure about this one
}
}
}
(I'm aware there might be some additional errors here - it's my first time working with UIKit in SwiftUI. Thanks for any help)
It should be differently, because view is struct, so your call updateTextStyle() in button action will have no effect, because applied to copy of above textField
Instead the approach should be like following (scratchy)
struct ContentView: View {
#State private var range: NSRange?
// example of style, on place of color your style
#State var color: Color = .black
var body: some View {
VStack {
MultilineTextField(rangeSelected: $range)
.foregroundColor(self.color) // style state dependency
Button(action: {
self.color = .red // specify new style
}) {
Text("Update text style")
}
}
}
}

#State updates view but #ObservedObject does not

I have a view:
struct Form: View {
#ObservedObject var model = FormInput()
var body: some View {
Form {
TextArea("Details", text: $model.details.value)
.validation(message: model.details.message)
}
}
}
Where TextArea is a custom view and .validation(message: model.details.message) is a custom view modifier. My FormInput looks as follows:
final class FormInput: ObservableObject {
#Published var details: TextInput
// ... other code
}
TextInput is a custom struct.
Now when I run the above code the validation never triggers because the Form never re-renders, but if I change the Form to:
struct MyForm: View {
#State var details = TextInput()
var body: some View {
Form {
TextArea("Details", text: $details.value)
.validation(message: details.message)
}
}
}
Then everything works as expected. Why would the view render for the second version of Form but not for the first? Shouldn't the Form in the first case update when details changes since details is #Published and the Form is observing the changes?
ADDITIONAL CONTEXT
Below is additional code for the above components
final class FormInput: ObservableObject {
#Published var details: TextInput
init(details: String = "") {
self.details = TextInput(value: details, isValid: false, validations: [.length(12)])
}
// other code
}
struct TextInput {
var value: String {
didSet {
self.validate()
}
}
var validations: [TextValidation] // this is just an enum of different types of validations
var isValid: Bool
var message: String
mutating func validate() {
for validation in validations {
// run validation
}
}
}
struct TextArea: View {
#Binding var text: String
#State var height: CGFloat = 12
init(text: Binding<String>) {
self._text = text
}
var body: some View {
TextAreaField(text: $text)
}
}
struct TextAreaField: UIViewRepresentable {
#Binding var text: String
#Binding var height: CGFloat
func makeUIView(context: Context) -> UITextView {
let textField = UITextView()
textField.isEditable = true
textField.isSelectable = true
textField.isUserInteractionEnabled = true
textField.isScrollEnabled = false
// ..other initializers removed for brevity
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: UITextView, context: Context) {
calculateHeight(uiView)
}
func calculateHeight(_ uiView: UIView) {
let newSize = uiView.sizeThatFits(CGSize(width: uiView.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
if self.height != newSize.height {
DispatchQueue.main.async {
self.height = newSize.height
}
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
final class Coordinator: NSObject, UITextViewDelegate {
var parent: TextAreaField
init(_ parent: TextAreaField) {
self.parent = parent
}
func textViewDidChange(_ uiView: UITextView) {
self.parent.text = uiView.text
self.parent.calculateHeight(uiView)
}
}
}
struct Validation: ViewModifier {
let message: String
func body(content: Content) -> some View {
let isValid = message == ""
print(message)
return VStack(alignment: .leading) {
content
if isValid == false {
Text(message)
.font(.footnote)
.italic()
.foregroundColor(Color.red)
.frame(height: 24)
}
}
.padding(.bottom, isValid ? 24 : 0)
}
}
extension View {
func validation(message: String) -> some View {
self.modifier(Validation(message: message))
}
}
I suppose the issue is in absent custom components. Because below simple demo replication of provided infrastructure works well with ObservableObject, actually as expected.
Tested with Xcode 11.4 / iOS 13.4. Comparing with below demo might be helpful to find what is missed in your code.
struct TextInput {
var value: String = "" {
didSet {
message = value // just duplication for demo
}
}
var message: String = ""
}
// simple validator highlighting text when too long
struct ValidationModifier: ViewModifier {
var text: String
func body(content: Content) -> some View {
content.foregroundColor(text.count > 5 ? Color.red : Color.primary)
}
}
extension View { // replicated
func validation(message: String) -> some View {
self.modifier(ValidationModifier(text: message))
}
}
struct MyForm: View { // avoid same names with standard views
#ObservedObject var model = FormInput()
var body: some View {
Form {
TextField("Details", text: $model.details.value) // used standard
.validation(message: model.details.message)
}
}
}
final class FormInput: ObservableObject {
#Published var details: TextInput = TextInput()
}
struct TestObservedInModifier: View {
var body: some View {
MyForm()
}
}

Autofocus TextField programmatically in SwiftUI

I'm using a modal to add names to a list. When the modal is shown, I want to focus the TextField automatically, like this:
I've not found any suitable solutions yet.
Is there anything implemented into SwiftUI already in order to do this?
Thanks for your help.
var modal: some View {
NavigationView{
VStack{
HStack{
Spacer()
TextField("Name", text: $inputText) // autofocus this!
.textFieldStyle(DefaultTextFieldStyle())
.padding()
.font(.system(size: 25))
// something like .focus() ??
Spacer()
}
Button(action: {
if self.inputText != ""{
self.players.append(Player(name: self.inputText))
self.inputText = ""
self.isModal = false
}
}, label: {
HStack{
Text("Add \(inputText)")
Image(systemName: "plus")
}
.font(.system(size: 20))
})
.padding()
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(10)
Spacer()
}
.navigationBarTitle("New Player")
.navigationBarItems(trailing: Button(action: {self.isModal=false}, label: {Text("Cancel").font(.system(size: 20))}))
.padding()
}
}
iOS 15
There is a new wrapper called #FocusState that controls the state of the keyboard and the focused keyboard ('aka' firstResponder).
Become First Responder ( Focused )
If you use a focused modifier on the text fields, you can make them become focused, for example, you can set the focusedField property in the code to make the binded textField become active:
Resign first responder ( Dismiss keyboard )
or dismiss the keyboard by setting the variable to nil:
Don't forget to watch the Direct and reflect focus in SwiftUI session from WWDC2021
iOS 13 and 14 (and 15)
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.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
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
}
Since Responder Chain is not presented to be consumed via SwiftUI, so we have to consume it using UIViewRepresentable.
I have made a workaround that can work similarly to the way we use to do using UIKit.
struct CustomTextField: UIViewRepresentable {
class Coordinator: NSObject, UITextFieldDelegate {
#Binding var text: String
#Binding var nextResponder : Bool?
#Binding var isResponder : Bool?
init(text: Binding<String>,nextResponder : Binding<Bool?> , isResponder : Binding<Bool?>) {
_text = text
_isResponder = isResponder
_nextResponder = nextResponder
}
func textFieldDidChangeSelection(_ textField: UITextField) {
text = textField.text ?? ""
}
func textFieldDidBeginEditing(_ textField: UITextField) {
DispatchQueue.main.async {
self.isResponder = true
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
DispatchQueue.main.async {
self.isResponder = false
if self.nextResponder != nil {
self.nextResponder = true
}
}
}
}
#Binding var text: String
#Binding var nextResponder : Bool?
#Binding var isResponder : Bool?
var isSecured : Bool = false
var keyboard : UIKeyboardType
func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.isSecureTextEntry = isSecured
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.keyboardType = keyboard
textField.delegate = context.coordinator
return textField
}
func makeCoordinator() -> CustomTextField.Coordinator {
return Coordinator(text: $text, nextResponder: $nextResponder, isResponder: $isResponder)
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
uiView.text = text
if isResponder ?? false {
uiView.becomeFirstResponder()
}
}
}
You can use this component like this...
struct ContentView : View {
#State private var username = ""
#State private var password = ""
// set true , if you want to focus it initially, and set false if you want to focus it by tapping on it.
#State private var isUsernameFirstResponder : Bool? = true
#State private var isPasswordFirstResponder : Bool? = false
var body : some View {
VStack(alignment: .center) {
CustomTextField(text: $username,
nextResponder: $isPasswordFirstResponder,
isResponder: $isUsernameFirstResponder,
isSecured: false,
keyboard: .default)
// assigning the next responder to nil , as this will be last textfield on the view.
CustomTextField(text: $password,
nextResponder: .constant(nil),
isResponder: $isPasswordFirstResponder,
isSecured: true,
keyboard: .default)
}
.padding(.horizontal, 50)
}
}
Here isResponder is to assigning responder to the current textfield, and nextResponder is to make the first response , as the current textfield resigns it.
SwiftUIX Solution
It's super easy with SwiftUIX and I am surprised more people are not aware about this.
Install SwiftUIX through Swift Package Manager.
In your code, import SwiftUIX.
Now you can use CocoaTextField instead of TextField to use the function .isFirstResponder(true).
CocoaTextField("Confirmation Code", text: $confirmationCode)
.isFirstResponder(true)
I think SwiftUIX has many handy stuff, but that is still the code outside of your control area and who knows what happens to that sugar magic when SwiftUI 3.0 comes out.
Allow me to present the boring UIKit solution slightly upgraded with reasonable checks and upgraded timing DispatchQueue.main.asyncAfter(deadline: .now() + 0.5)
// AutoFocusTextField.swift
struct AutoFocusTextField: UIViewRepresentable {
private let placeholder: String
#Binding private var text: String
private let onEditingChanged: ((_ focused: Bool) -> Void)?
private let onCommit: (() -> Void)?
init(_ placeholder: String, text: Binding<String>, onEditingChanged: ((_ focused: Bool) -> Void)? = nil, onCommit: (() -> Void)? = nil) {
self.placeholder = placeholder
_text = text
self.onEditingChanged = onEditingChanged
self.onCommit = onCommit
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<AutoFocusTextField>) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
textField.placeholder = placeholder
return textField
}
func updateUIView(_ uiView: UITextField, context:
UIViewRepresentableContext<AutoFocusTextField>) {
uiView.text = text
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // needed for modal view to show completely before aufo-focus to avoid crashes
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 ?? ""
}
func textFieldDidEndEditing(_ textField: UITextField) {
parent.onEditingChanged?(false)
}
func textFieldDidBeginEditing(_ textField: UITextField) {
parent.onEditingChanged?(true)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
parent.onCommit?()
return true
}
}
}
// SearchBarView.swift
struct SearchBarView: View {
#Binding private var searchText: String
#State private var showCancelButton = false
private var shouldShowOwnCancelButton = true
private let onEditingChanged: ((Bool) -> Void)?
private let onCommit: (() -> Void)?
#Binding private var shouldAutoFocus: Bool
init(searchText: Binding<String>,
shouldShowOwnCancelButton: Bool = true,
shouldAutofocus: Binding<Bool> = .constant(false),
onEditingChanged: ((Bool) -> Void)? = nil,
onCommit: (() -> Void)? = nil) {
_searchText = searchText
self.shouldShowOwnCancelButton = shouldShowOwnCancelButton
self.onEditingChanged = onEditingChanged
_shouldAutoFocus = shouldAutofocus
self.onCommit = onCommit
}
var body: some View {
HStack {
HStack(spacing: 6) {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray500)
.font(Font.subHeadline)
.opacity(1)
if shouldAutoFocus {
AutoFocusTextField("Search", text: $searchText) { focused in
self.onEditingChanged?(focused)
self.showCancelButton.toggle()
}
.foregroundColor(.gray600)
.font(Font.body)
} else {
TextField("Search", text: $searchText, onEditingChanged: { focused in
self.onEditingChanged?(focused)
self.showCancelButton.toggle()
}, onCommit: {
print("onCommit")
}).foregroundColor(.gray600)
.font(Font.body)
}
Button(action: {
self.searchText = ""
}) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.gray500)
.opacity(searchText == "" ? 0 : 1)
}.padding(4)
}.padding([.leading, .trailing], 8)
.frame(height: 36)
.background(Color.gray300.opacity(0.6))
.cornerRadius(5)
if shouldShowOwnCancelButton && showCancelButton {
Button("Cancel") {
UIApplication.shared.endEditing(true) // this must be placed before the other commands here
self.searchText = ""
self.showCancelButton = false
}
.foregroundColor(Color(.systemBlue))
}
}
}
}
#if DEBUG
struct SearchBarView_Previews: PreviewProvider {
static var previews: some View {
Group {
SearchBarView(searchText: .constant("Art"))
.environment(\.colorScheme, .light)
SearchBarView(searchText: .constant("Test"))
.environment(\.colorScheme, .dark)
}
}
}
#endif
// MARK: Helpers
extension UIApplication {
func endEditing(_ force: Bool) {
self.windows
.filter{$0.isKeyWindow}
.first?
.endEditing(force)
}
}
// ContentView.swift
class SearchVM: ObservableObject {
#Published var searchQuery: String = ""
...
}
struct ContentView: View {
#State private var shouldAutofocus = true
#StateObject private var viewModel = SearchVM()
var body: some View {
VStack {
SearchBarView(searchText: $query, shouldShowOwnCancelButton: false, shouldAutofocus: $shouldAutofocus)
}
}
}
For macOS 13, there is a new modifier that does not require a delay. Currently, does not work on iOS 16.
VStack {
TextField(...)
.focused($focusedField, equals: .firstField)
TextField(...)
.focused($focusedField, equals: .secondField)
}.defaultFocus($focusedField, .secondField) // <== Here
Apple Documentation: defaultFocus()