| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- //
- // DismissibleKeyboardTextField.swift
- // LoopKitUI
- //
- // Created by Michael Pangburn on 7/22/20.
- // Copyright © 2020 LoopKit Authors. All rights reserved.
- //
- import SwiftUI
- public struct DismissibleKeyboardTextField: UIViewRepresentable {
- @Binding var text: String
- var placeholder: String
- var font: UIFont
- var textColor: UIColor
- var textAlignment: NSTextAlignment
- var keyboardType: UIKeyboardType
- var autocapitalizationType: UITextAutocapitalizationType
- var autocorrectionType: UITextAutocorrectionType
- var shouldBecomeFirstResponder: Bool
- var maxLength: Int?
- public init(
- text: Binding<String>,
- placeholder: String,
- font: UIFont = .preferredFont(forTextStyle: .body),
- textColor: UIColor = .label,
- textAlignment: NSTextAlignment = .natural,
- keyboardType: UIKeyboardType = .default,
- autocapitalizationType: UITextAutocapitalizationType = .sentences,
- autocorrectionType: UITextAutocorrectionType = .default,
- shouldBecomeFirstResponder: Bool = false,
- maxLength: Int? = nil
- ) {
- self._text = text
- self.placeholder = placeholder
- self.font = font
- self.textColor = textColor
- self.textAlignment = textAlignment
- self.keyboardType = keyboardType
- self.autocapitalizationType = autocapitalizationType
- self.autocorrectionType = autocorrectionType
- self.shouldBecomeFirstResponder = shouldBecomeFirstResponder
- self.maxLength = maxLength
- }
- public func makeUIView(context: Context) -> UITextField {
- let textField = UITextField()
- textField.inputAccessoryView = makeDoneToolbar(for: textField)
- textField.addTarget(context.coordinator, action: #selector(Coordinator.textChanged), for: .editingChanged)
- textField.addTarget(context.coordinator, action: #selector(Coordinator.editingDidBegin), for: .editingDidBegin)
- textField.delegate = context.coordinator
- return textField
- }
- private func makeDoneToolbar(for textField: UITextField) -> UIToolbar {
- let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
- let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
- let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: textField, action: #selector(UITextField.resignFirstResponder))
- doneButton.tintColor = textColor
- toolbar.items = [flexibleSpace, doneButton]
- toolbar.sizeToFit()
- return toolbar
- }
- public func updateUIView(_ textField: UITextField, context: Context) {
- textField.text = text
- textField.placeholder = placeholder
- textField.font = font
- textField.textColor = textColor
- textField.textAlignment = textAlignment
- textField.keyboardType = keyboardType
- textField.autocapitalizationType = autocapitalizationType
- textField.autocorrectionType = autocorrectionType
- if shouldBecomeFirstResponder && !context.coordinator.didBecomeFirstResponder {
- // See https://developer.apple.com/documentation/uikit/uiresponder/1621113-becomefirstresponder for why
- // we check the window property here (otherwise it might crash)
- if textField.window != nil && textField.becomeFirstResponder() {
- context.coordinator.didBecomeFirstResponder = true
- }
- } else if !shouldBecomeFirstResponder && context.coordinator.didBecomeFirstResponder {
- context.coordinator.didBecomeFirstResponder = false
- }
- }
- public func makeCoordinator() -> Coordinator {
- Coordinator(self, maxLength: maxLength)
- }
- public final class Coordinator: NSObject {
- var parent: DismissibleKeyboardTextField
- let maxLength: Int?
- // Track in the coordinator to ensure the text field only becomes first responder once,
- // rather than on every state change.
- var didBecomeFirstResponder = false
- init(_ parent: DismissibleKeyboardTextField, maxLength: Int?) {
- self.parent = parent
- self.maxLength = maxLength
- }
- @objc fileprivate func textChanged(_ textField: UITextField) {
- parent.text = textField.text ?? ""
- }
-
- @objc fileprivate func editingDidBegin(_ textField: UITextField) {
- // Even though we are likely already on .main, we still need to queue this cursor (selection) change in
- // order for it to work
- DispatchQueue.main.async {
- textField.moveCursorToEnd()
- }
- }
- }
- }
- extension DismissibleKeyboardTextField.Coordinator: UITextFieldDelegate {
- public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
- guard let maxLength = maxLength else {
- return true
- }
- let currentString: NSString = (textField.text ?? "") as NSString
- let newString: NSString =
- currentString.replacingCharacters(in: range, with: string) as NSString
- return newString.length <= maxLength
- }
- }
|