Skip to content

Instantly share code, notes, and snippets.

@AdamWhitcroft
Last active January 24, 2024 14:22
Show Gist options
  • Save AdamWhitcroft/8484f2672434777f1daf2d18f710e633 to your computer and use it in GitHub Desktop.
Save AdamWhitcroft/8484f2672434777f1daf2d18f710e633 to your computer and use it in GitHub Desktop.
WrappedUITextField
import SwiftUI
import UIKit
/*
This is how I've used UIViewRepresentable for wrapping a UITextField
for the Search input of my app Daypage.
Usage:
SearchInputView(
placeholderText: Strings.SearchInputPrompt,
searchQuery: $searchQuery,
isFirstResponder: $isFirstResponder
)
For my specific use case, I've chosen to make `isFirstResponder`
a binding, so I can let the parent view inform if this field should
be focussed when the view appears or not, like so:
@State private var isFirstResponder: Bool = true
*/
struct SearchInputView: UIViewRepresentable {
// MARK: - Properties
var placeholderText: String
// MARK: - Binding
@Binding var searchQuery: String
@Binding var isFirstResponder: Bool
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
textField.placeholder = placeholderText
textField.text = searchQuery
textField.returnKeyType = .search
return textField
}
/// Makes the view act closer to a SwiftUI
/// view in terms of respecting the size
/// of it's parent container.
func sizeThatFits(
_ proposal: ProposedViewSize,
uiView: UITextField, context: Context
) -> CGSize? {
guard
let width = proposal.width,
let height = proposal.height
else { return nil }
return CGSize(width: width, height: height)
}
/// Updates the `UITextField` instance.
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = searchQuery
/// Handles if our `TextView` should enter
/// focused or not.
if isFirstResponder {
uiView.becomeFirstResponder()
}
}
/// Makes the coordinator to handle text changes.
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: SearchInputView
init(_ parent: SearchInputView) {
self.parent = parent
}
func textFieldDidChangeSelection(_ textField: UITextField) {
/// By using `DispatchQueue.main.async` we ensure that the update to
/// `searchQuery` occurs after the current cycle of view updates completes,
/// avoiding the modification of state during a view update.
DispatchQueue.main.async {
self.parent.searchQuery = textField.text ?? ""
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder() // Dismiss the keyboard
return true
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment