SecureField.swift 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. /// A control that displays an editable text interface, hiding characters
  2. /// as they're typed.
  3. public struct SecureField: ElementaryView, View {
  4. /// The ideal width of a `SecureField`.
  5. private static let idealWidth: Double = 100
  6. /// The label to show when the field is empty.
  7. private var placeholder: String
  8. /// The field's content.
  9. @Binding private var text: String
  10. /// Creates an editable secure text field with a given placeholder.
  11. ///
  12. /// - Parameters:
  13. /// - placeholder: The label to show when the field is empty.
  14. /// - text: The field's content.
  15. public init(_ placeholder: String = "", text: Binding<String>) {
  16. self.placeholder = placeholder
  17. self._text = text
  18. }
  19. func asWidget<Backend: BaseAppBackend>(backend: Backend) -> Backend.Widget {
  20. return backend.createSecureField()
  21. }
  22. func computeLayout<Backend: BaseAppBackend>(
  23. _ widget: Backend.Widget,
  24. proposedSize: ProposedViewSize,
  25. environment: EnvironmentValues,
  26. backend: Backend
  27. ) -> ViewLayoutResult {
  28. let naturalHeight = backend.naturalSize(of: widget).y
  29. let size = ViewSize(
  30. proposedSize.width ?? Self.idealWidth,
  31. Double(naturalHeight)
  32. )
  33. // TODO: Allow backends to set their own ideal text field width
  34. return ViewLayoutResult.leafView(size: size)
  35. }
  36. func commit<Backend: BaseAppBackend>(
  37. _ widget: Backend.Widget,
  38. layout: ViewLayoutResult,
  39. environment: EnvironmentValues,
  40. backend: Backend
  41. ) {
  42. backend.updateSecureField(
  43. widget,
  44. placeholder: placeholder,
  45. environment: environment,
  46. onChange: { newValue in
  47. #if DEBUG
  48. // We perform this check in debug mode to catch backends that cause
  49. // unnecessary binding writes, but avoid doing so in release mode
  50. // because comparing text may often be more expensive than just
  51. // avoiding the additional write at the backend level. These
  52. // additional writes are often the result of the handler being
  53. // triggered when we call backend.setContent(ofTextField:to:)
  54. if self.text == newValue {
  55. logger.warning(
  56. """
  57. Unnecessary write to text Binding of SecureField detected, \
  58. please open an issue at \(Meta.issueReportingURL) \
  59. so we can fix it for \(type(of: backend)).
  60. """
  61. )
  62. }
  63. #endif
  64. self.text = newValue
  65. },
  66. onSubmit: environment.onSubmit ?? {}
  67. )
  68. let text = text
  69. if text != backend.getContent(ofSecureField: widget) {
  70. backend.setContent(ofSecureField: widget, to: text)
  71. }
  72. backend.setSize(of: widget, to: layout.size.vector)
  73. }
  74. }