Skip to content

Instantly share code, notes, and snippets.

@MaxenceMottard
Last active October 28, 2021 12:53
Show Gist options
  • Save MaxenceMottard/9950a23ab2a721c16cc8ef59cc1aa5f7 to your computer and use it in GitHub Desktop.
Save MaxenceMottard/9950a23ab2a721c16cc8ef59cc1aa5f7 to your computer and use it in GitHub Desktop.
UITextField implementation in SwiftUI
import SwiftUI
struct GenericTextField: UIViewRepresentable {
// MARK: States
@Binding private var text: String
@Binding private var isEditing: Bool
@Binding private var isFirstResponder: Bool
private var formatText: (String, String) -> (String) = { _, newValue in
return newValue
}
private var tapReturnKey: () -> () = {}
// MARK: TextField Configurations
private var isDisabled: Bool = false
private var isSecureTextEntry: Bool = false
private var font: UIFont?
private var textColor: UIColor?
private var tintColor: UIColor?
private var textAlignment: NSTextAlignment = .left
private var horizontalHuggingPriority: UILayoutPriority = .fittingSizeLevel
private var verticalHuggingPriority: UILayoutPriority = .required
private var textConfiguration: TextConfiguration = TextConfiguration()
private var passwordRules: UITextInputPasswordRules?
init(_ text: Binding<String>, isEditing: Binding<Bool> = .constant(false), isFirstResponder: Binding<Bool> = .constant(false)) {
self._text = text
self._isEditing = isEditing
self._isFirstResponder = isFirstResponder
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
textField.backgroundColor = .clear
setTextField(textField: textField)
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
setTextField(textField: uiView)
}
private func setTextField(textField: UITextField) {
if self.text != textField.text {
textField.text = text
}
textField.isEnabled = !isDisabled
textField.isSecureTextEntry = isSecureTextEntry
textField.textColor = textColor ?? UIColor.black
textField.tintColor = tintColor
textField.font = font
textField.passwordRules = passwordRules
textField.keyboardType = textConfiguration.keyboardType
textField.returnKeyType = textConfiguration.returnKeyType
textField.textContentType = textConfiguration.textContentType
textField.autocorrectionType = textConfiguration.autoCorrectionType
textField.autocapitalizationType = textConfiguration.autocapitalizationType
textField.setContentHuggingPriority(horizontalHuggingPriority, for: .horizontal)
textField.setContentHuggingPriority(verticalHuggingPriority, for: .vertical)
if isFirstResponder {
DispatchQueue.main.async {
textField.becomeFirstResponder()
isFirstResponder = false
}
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text, isEditing: $isEditing, formatText: formatText, tapReturnKey: tapReturnKey)
}
class Coordinator: NSObject, UITextFieldDelegate {
private var formatText: (String, String) -> (String)
private var tapReturnKey: () -> ()
@Binding private var text: String
@Binding private var isEditing: Bool
init(text: Binding<String>, isEditing: Binding<Bool>,
formatText: @escaping (String, String) -> (String),
tapReturnKey: @escaping () -> ()) {
self._text = text
self._isEditing = isEditing
self.formatText = formatText
self.tapReturnKey = tapReturnKey
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if (range == NSRange(location: 0, length: 0) && (string == "" || string == " ")) {
return true
}
withAnimation { [weak self] in
guard let self = self else { return }
self.text = self.setCursorAndText(for: string, with: range, in: textField)
}
return false
}
private func setCursorAndText(for string: String, with range: NSRange, in textField: UITextField) -> String {
// Detect Autofill
let didAutofillTextfield = (range == NSRange(location: 0, length: 0) && (string.count > 1))
if didAutofillTextfield {
return formatText(text, string)
}
guard let start = textField.position(from: textField.beginningOfDocument, offset: range.location),
let textFieldText = textField.text,
let textRange = Range(range, in: textFieldText) else {
return string
}
let newText = textFieldText.replacingCharacters(in: textRange, with: string)
let formattedText = formatText(text, newText)
textField.text = formattedText
let diffCount = formattedText.count - text.count < -1
? abs(formattedText.count - text.count)
: max(formattedText.count - text.count, 0)
let cursorOffset = textField.offset(from: textField.beginningOfDocument, to: start) + diffCount
guard let cursorPosition = textField.position(from: textField.beginningOfDocument, offset: cursorOffset) else {
return formattedText
}
textField.selectedTextRange = textField.textRange(from: cursorPosition, to: cursorPosition)
return formattedText
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
tapReturnKey()
return false
}
func textFieldDidBeginEditing(_ textField: UITextField) {
withAnimation {
isEditing = true
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
withAnimation {
isEditing = false
}
}
}
struct TextConfiguration {
let keyboardType: UIKeyboardType
let returnKeyType: UIReturnKeyType
let textContentType: UITextContentType?
let autoCorrectionType: UITextAutocorrectionType
let autocapitalizationType: UITextAutocapitalizationType
init(keyboardType: UIKeyboardType = .default,
textContentType: UITextContentType? = nil,
autoCorrectionType: UITextAutocorrectionType = .default,
autocapitalizationType: UITextAutocapitalizationType = .sentences,
returnKeyType: UIReturnKeyType = .default) {
self.keyboardType = keyboardType
self.textContentType = textContentType
self.autoCorrectionType = autoCorrectionType
self.autocapitalizationType = autocapitalizationType
self.returnKeyType = returnKeyType
}
}
}
// MARK: Modifiers
extension GenericTextField {
func isDisabled(_ isDisabled: Bool) -> GenericTextField {
var view = self
view.isDisabled = isDisabled
return view
}
func isSecureTextEntry(_ isSecureTextEntry: Bool) -> GenericTextField {
var view = self
view.isSecureTextEntry = isSecureTextEntry
return view
}
func textContentType(_ textContentType: UITextContentType) -> GenericTextField {
var view = self
view.textConfiguration = TextConfiguration(keyboardType: textConfiguration.keyboardType, textContentType: textContentType, autoCorrectionType: textConfiguration.autoCorrectionType, autocapitalizationType: textConfiguration.autocapitalizationType)
return view
}
func formatOnTextChange(_ closure: @escaping (String, String) -> (String)) -> GenericTextField {
var view = self
view.formatText = closure
return view
}
func tintColor(_ tintColor: Color?) -> GenericTextField {
var view = self
if let cgColor = tintColor?.cgColor {
view.tintColor = UIColor(cgColor: cgColor)
}
return view
}
func textColor(_ textColor: UIColor?) -> GenericTextField {
var view = self
if let cgColor = textColor?.cgColor {
view.textColor = UIColor(cgColor: cgColor)
}
return view
}
func textAlignment(_ textAlignment: NSTextAlignment) -> GenericTextField {
var view = self
view.textAlignment = textAlignment
return view
}
func font(_ font: UIFont?) -> GenericTextField {
var view = self
view.font = font
return view
}
func dontFillWidth() -> GenericTextField {
var view = self
view.horizontalHuggingPriority = .required
return view
}
func textConfiguration(_ configuration: TextConfiguration) -> GenericTextField {
var view = self
view.textConfiguration = configuration
return view
}
func keyboardType(_ keyboardType: UIKeyboardType) -> GenericTextField {
var view = self
view.textConfiguration = TextConfiguration(keyboardType: keyboardType, textContentType: textConfiguration.textContentType, autoCorrectionType: textConfiguration.autoCorrectionType, autocapitalizationType: textConfiguration.autocapitalizationType)
return view
}
func passwordRules(_ descriptor: String?) -> GenericTextField {
var view = self
if let descriptor = descriptor {
view.passwordRules = UITextInputPasswordRules(descriptor: descriptor)
}
return view
}
func onTapReturnKey(_ closure: @escaping () -> ()) -> GenericTextField {
var view = self
view.tapReturnKey = closure
return view
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment