I am trying to implement an inputAccessoryView on a TextField in SwiftUI. The goal is to have a "Done" Button appear above the Keyboard which when pressed gets rid of the keyboard (i.e. resignFirstResponder()).
I came across the following Medium article which purports to implement this behavior exactly as I would require, however, I am struggling to get it working.
Medium link containing method to be implemented.
I have tried to implement this in a blank XCode project, my code compiles, however, the TextField never shows up, and I cannot touch in the area it should be to bring up the keyboard. How do I correctly implement this code to get the desired behavior?
Code
import Foundation
import UIKit
import SwiftUI
class TextFieldViewController
: UIViewController {
// our custom text field will report changes to the outside
let text: Binding<String>?
// if the toolbar (see below) is used (Done), the keyboard shall be dismissed
// and optionally we execute a provided closure
let onDismiss: (() -> Void)?
init (
text: Binding<String>
, onDismiss: (() -> Void)?) {
self.text = text
self.onDismiss = onDismiss
super.init(
nibName: nil //"<XIB>"
, bundle: nil //Bundle.main?
)
}
required init?(coder: NSCoder) {
self.text = nil
self.onDismiss = nil
super.init(coder: coder)
}
// helper function to encapsulate calling the "view" of UIViewController
fileprivate func getTextField() -> UITextField? {
return view as? UITextField
}
override func viewDidLoad() {
let textField = self.getTextField()
guard textField != nil else {
return
}
// configure a toolbar with a Done button
let toolbar = UIToolbar()
toolbar.setItems([
// just moves the Done item to the right
UIBarButtonItem(
barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace
, target: nil
, action: nil
)
, UIBarButtonItem(
title: "Done"
, style: UIBarButtonItem.Style.done
, target: self
, action: #selector(self.onSet)
)
]
, animated: true
)
toolbar.barStyle = UIBarStyle.default
toolbar.sizeToFit()
textField?.inputAccessoryView = toolbar
}
@objc private func onSet() {
let textField = self.getTextField()
textField?.resignFirstResponder()
self.text?.wrappedValue = textField?.text ?? ""
self.onDismiss?()
}
}
// The SwiftUI view, wrapping the UITextField
struct TextFieldView: View {
var text: Binding<String>
var onDismissKeyboard: (() -> Void)?
var body: some View {
TextFieldRepresentable(
text: self.text
, dismissKeyboardCallback: self.onDismissKeyboard
)
}
}
// The UIViewControllerRepresentable, feeding and controlling the UIViewController
struct TextFieldRepresentable
: UIViewControllerRepresentable {
// the callback
let dismissKeyboardCallback: (() -> Void)?
// created in the previous file/gist
let viewController: TextFieldViewController
init (
text: Binding<String>
, dismissKeyboardCallback: (() -> Void)?) {
self.dismissKeyboardCallback = dismissKeyboardCallback
self.viewController = TextFieldViewController(
text: text
, onDismiss: dismissKeyboardCallback
)
}
// UIViewControllerRepresentable
func makeUIViewController(context: Context) -> UIViewController {
return viewController
}
// UIViewControllerRepresentable
func updateUIViewController(_ viewController: UIViewController, context: Context) {
}
}
struct ContentView : View {
@State var email:String = ""
var body: some View {
HStack{
Circle()
TextFieldView(text: $email)
Circle()
}
}
}