RowTextfield.swift 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. //
  2. // RowTextField.swift
  3. // LoopKitUI
  4. //
  5. // Created by Noah Brauner on 7/20/23.
  6. // Copyright © 2023 LoopKit Authors. All rights reserved.
  7. //
  8. import SwiftUI
  9. /// A text field that supports custom input keyboards, moves the cursor to the end of the text, becomes the first responder when it's the focused row, and loses first responder when it's not
  10. struct RowTextField: UIViewRepresentable {
  11. @Binding var text: String
  12. @Binding var isFocused: Bool
  13. var maxLength: Int? = nil
  14. var configuration = { (view: CustomInputTextField) in }
  15. func makeCoordinator() -> Coordinator {
  16. return Coordinator(text: $text, isFocused: $isFocused, maxLength: maxLength)
  17. }
  18. func makeUIView(context: UIViewRepresentableContext<RowTextField>) -> CustomInputTextField {
  19. let textField = CustomInputTextField(frame: .zero)
  20. textField.delegate = context.coordinator
  21. textField.addTarget(context.coordinator, action: #selector(Coordinator.textChanged), for: .editingChanged)
  22. return textField
  23. }
  24. func updateUIView(_ textField: CustomInputTextField, context: UIViewRepresentableContext<RowTextField>) {
  25. textField.text = text
  26. configuration(textField)
  27. DispatchQueue.main.async {
  28. if isFocused && !textField.isFirstResponder {
  29. textField.becomeFirstResponder()
  30. } else if !isFocused && textField.isFirstResponder {
  31. textField.resignFirstResponder()
  32. }
  33. }
  34. }
  35. class Coordinator: NSObject, UITextFieldDelegate {
  36. @Binding var text: String
  37. @Binding var isFocused: Bool
  38. let maxLength: Int?
  39. init(text: Binding<String>, isFocused: Binding<Bool>, maxLength: Int?) {
  40. self._text = text
  41. self._isFocused = isFocused
  42. self.maxLength = maxLength
  43. }
  44. @objc fileprivate func textChanged(_ textField: UITextField) {
  45. DispatchQueue.main.async {
  46. self.text = textField.text ?? ""
  47. }
  48. }
  49. func textFieldDidBeginEditing(_ textField: UITextField) {
  50. DispatchQueue.main.async { [weak textField] in
  51. textField?.selectedTextRange = textField?.textRange(from: textField!.endOfDocument, to: textField!.endOfDocument)
  52. }
  53. withAnimation {
  54. isFocused = true
  55. }
  56. }
  57. func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
  58. if isFocused {
  59. isFocused = false
  60. }
  61. return true
  62. }
  63. func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  64. guard let maxLength = maxLength else {
  65. return true
  66. }
  67. let currentString: NSString = (textField.text ?? "") as NSString
  68. let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString
  69. return newString.length <= maxLength
  70. }
  71. }
  72. }