TogglableSecureInput.swift 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. // LoopFollow
  2. // TogglableSecureInput.swift
  3. // Created by Jonas Björkert.
  4. import SwiftUI
  5. struct TogglableSecureInput: View {
  6. enum Style { case singleLine, multiLine }
  7. let placeholder: String
  8. @Binding var text: String
  9. let style: Style
  10. var textContentType: UITextContentType? = nil
  11. @State private var isVisible = false
  12. @FocusState private var isFocused: Bool
  13. var body: some View {
  14. HStack(alignment: .top) {
  15. Group {
  16. switch style {
  17. case .singleLine:
  18. if isVisible {
  19. TextField(placeholder, text: $text)
  20. .multilineTextAlignment(.trailing)
  21. .textContentType(textContentType)
  22. } else {
  23. SecureField(placeholder, text: $text)
  24. .multilineTextAlignment(.trailing)
  25. .textContentType(textContentType)
  26. }
  27. case .multiLine:
  28. ZStack(alignment: .topLeading) {
  29. TextEditor(text: $text)
  30. .opacity(isVisible ? 1 : 0)
  31. .focused($isFocused)
  32. .frame(minHeight: 100)
  33. if !isVisible {
  34. Text(maskString)
  35. .font(.body.monospaced())
  36. .foregroundColor(.primary)
  37. .frame(maxWidth: .infinity,
  38. maxHeight: .infinity,
  39. alignment: .topLeading)
  40. .padding(.top, 8)
  41. .padding(.leading, 5)
  42. }
  43. }
  44. }
  45. }
  46. .textInputAutocapitalization(.never)
  47. .autocorrectionDisabled()
  48. .privacySensitive()
  49. .submitLabel(.done)
  50. Button { isVisible.toggle() } label: {
  51. Image(systemName: isVisible ? "eye.slash" : "eye")
  52. .foregroundColor(.secondary)
  53. }
  54. .buttonStyle(.plain)
  55. .padding(.leading, 4)
  56. }
  57. .contentShape(Rectangle())
  58. .onTapGesture { isFocused = true }
  59. }
  60. private var maskString: String {
  61. text.map { $0.isNewline ? "\n" : "•" }.joined()
  62. }
  63. }