Skip to content

Instantly share code, notes, and snippets.

@liamnichols
Last active March 14, 2024 12:22
Show Gist options
  • Save liamnichols/a2e656ae93a597952b4427bcfa371185 to your computer and use it in GitHub Desktop.
Save liamnichols/a2e656ae93a597952b4427bcfa371185 to your computer and use it in GitHub Desktop.
Embed a SwiftUI view inside a UITextView/UITextField inputAccessoryView to present it above the keyboard
import SwiftUI
import UIKit
class ViewController: UIViewController {
private lazy var textField: UITextField = {
let textField = UITextField()
textField.borderStyle = .roundedRect
textField.translatesAutoresizingMaskIntoConstraints = false
textField.setInputAccessoryView(height: 56) {
ScrollView(.horizontal) {
HStack(spacing: 8) {
Button("Print Message", action: { print("Do Foo!") })
.buttonStyle(.borderedProminent)
Button("Dismiss", action: { [weak self] in self?.textField.resignFirstResponder() })
.buttonStyle(.borderedProminent)
}
.padding(.horizontal, 8)
}
.frame(maxHeight: .infinity)
.background(Color.red)
}
return textField
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textField)
NSLayoutConstraint.activate([
textField.topAnchor.constraint(equalTo: view.topAnchor, constant: 8),
textField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8),
textField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8),
])
}
}
import SwiftUI
import UIKit
private var inputAccessoryViewControllerKey: Void?
extension UIView {
public override var inputAccessoryViewController: UIInputViewController? {
get { objc_getAssociatedObject(self, &inputAccessoryViewControllerKey) as? UIInputViewController }
set { objc_setAssociatedObject(self, &inputAccessoryViewControllerKey, newValue, .OBJC_ASSOCIATION_RETAIN) }
}
func setInputAccessoryView<Content: View>(height: CGFloat, @ViewBuilder content: () -> Content) {
let viewController = InputHostingViewController(rootView: content())
viewController.preferredContentSize.height = height
inputAccessoryViewController = viewController
}
}
/// `UIInputViewController` subclass that wraps a `UIHostingController` allowing you to embed SwiftUI inside `inputAccessoryViewController` and friends.
class InputHostingViewController<Content: View>: UIInputViewController {
let hostingViewController: UIHostingController<Content>
init(rootView: Content) {
self.hostingViewController = UIHostingController(rootView: rootView)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.translatesAutoresizingMaskIntoConstraints = false
addChild(hostingViewController)
hostingViewController.loadViewIfNeeded()
hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(hostingViewController.view)
NSLayoutConstraint.activate([
hostingViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
hostingViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
hostingViewController.didMove(toParent: self)
}
override var preferredContentSize: CGSize {
didSet {
view.bounds.size.height = preferredContentSize.height
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment