| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768 |
- // LoopFollow
- // TogglableSecureInput.swift
- // Created by Jonas Björkert on 2025-05-28.
- import SwiftUI
- struct TogglableSecureInput: View {
- enum Style { case singleLine, multiLine }
- let placeholder: String
- @Binding var text: String
- let style: Style
- @State private var isVisible = false
- @FocusState private var isFocused: Bool
- var body: some View {
- HStack(alignment: .top) {
- Group {
- switch style {
- case .singleLine:
- if isVisible {
- TextField(placeholder, text: $text).multilineTextAlignment(.trailing)
- } else {
- SecureField(placeholder, text: $text).multilineTextAlignment(.trailing)
- }
- case .multiLine:
- ZStack(alignment: .topLeading) {
- TextEditor(text: $text)
- .opacity(isVisible ? 1 : 0)
- .focused($isFocused)
- .frame(minHeight: 100)
- if !isVisible {
- Text(maskString)
- .font(.body.monospaced())
- .foregroundColor(.primary)
- .frame(maxWidth: .infinity,
- maxHeight: .infinity,
- alignment: .topLeading)
- .padding(.top, 8)
- .padding(.leading, 5)
- }
- }
- }
- }
- .textInputAutocapitalization(.never)
- .autocorrectionDisabled()
- .privacySensitive()
- .submitLabel(.done)
- Button { isVisible.toggle() } label: {
- Image(systemName: isVisible ? "eye.slash" : "eye")
- .foregroundColor(.secondary)
- }
- .buttonStyle(.plain)
- .padding(.leading, 4)
- }
- .contentShape(Rectangle())
- .onTapGesture { isFocused = true }
- }
- private var maskString: String {
- text.map { $0.isNewline ? "\n" : "•" }.joined()
- }
- }
|