@@ -22,18 +22,56 @@ struct AccountCreationFormFieldViewModel {
2222struct AccountCreationFormFieldView : View {
2323 private let viewModel : AccountCreationFormFieldViewModel
2424
25+ /// Whether the text field is *shown* as secure.
26+ /// When the field is secure, there is a button to show/hide the text field input.
27+ @State private var showsSecureInput : Bool = true
28+
29+ // Tracks the scale of the view due to accessibility changes.
30+ @ScaledMetric private var scale : CGFloat = 1.0
31+
2532 init ( viewModel: AccountCreationFormFieldViewModel ) {
2633 self . viewModel = viewModel
34+ self . showsSecureInput = viewModel. isSecure
2735 }
2836
2937 var body : some View {
3038 VStack ( alignment: . leading, spacing: Layout . verticalSpacing) {
3139 Text ( viewModel. header)
3240 . subheadlineStyle ( )
3341 if viewModel. isSecure {
34- SecureField ( viewModel. placeholder, text: viewModel. text)
35- . textFieldStyle ( RoundedBorderTextFieldStyle ( focused: viewModel. isFocused) )
42+ ZStack ( alignment: . trailing) {
43+ // Text field based on the `isTextFieldSecure` state.
44+ Group {
45+ if showsSecureInput {
46+ SecureField ( viewModel. placeholder, text: viewModel. text)
47+ } else {
48+ TextField ( viewModel. placeholder, text: viewModel. text)
49+ }
50+ }
51+ . font ( . body)
52+ . textFieldStyle ( RoundedBorderTextFieldStyle (
53+ focused: viewModel. isFocused,
54+ // Custom insets to leave trailing space for the reveal button.
55+ insets: . init( top: RoundedBorderTextFieldStyle . Defaults. insets. top,
56+ leading: RoundedBorderTextFieldStyle . Defaults. insets. leading,
57+ bottom: RoundedBorderTextFieldStyle . Defaults. insets. bottom,
58+ trailing: Layout . secureFieldRevealButtonHorizontalPadding * 2 + Layout. secureFieldRevealButtonDimension * scale) ,
59+ height: 44 * scale
60+ ) )
3661 . keyboardType ( viewModel. keyboardType)
62+
63+ // Button to show/hide the text field content.
64+ Button ( action: {
65+ showsSecureInput. toggle ( )
66+ } ) {
67+ Image ( systemName: showsSecureInput ? " eye.slash " : " eye " )
68+ . accentColor ( Color ( . textSubtle) )
69+ . frame ( width: Layout . secureFieldRevealButtonDimension * scale,
70+ height: Layout . secureFieldRevealButtonDimension * scale)
71+ . padding ( . leading, Layout . secureFieldRevealButtonHorizontalPadding)
72+ . padding ( . trailing, Layout . secureFieldRevealButtonHorizontalPadding)
73+ }
74+ }
3775 } else {
3876 TextField ( viewModel. placeholder, text: viewModel. text)
3977 . textFieldStyle ( RoundedBorderTextFieldStyle ( focused: viewModel. isFocused) )
@@ -50,6 +88,8 @@ struct AccountCreationFormFieldView: View {
5088private extension AccountCreationFormFieldView {
5189 enum Layout {
5290 static let verticalSpacing : CGFloat = 8
91+ static let secureFieldRevealButtonHorizontalPadding : CGFloat = 16
92+ static let secureFieldRevealButtonDimension : CGFloat = 18
5393 }
5494}
5595
@@ -62,12 +102,24 @@ struct AccountCreationFormField_Previews: PreviewProvider {
62102 isSecure: false ,
63103 errorMessage: nil ,
64104 isFocused: true ) )
65- AccountCreationFormFieldView ( viewModel: . init( header: " Choose a password " ,
66- placeholder: " Password " ,
67- keyboardType: . default,
68- text: . constant( " w " ) ,
69- isSecure: true ,
70- errorMessage: " Too simple " ,
71- isFocused: false ) )
105+ VStack {
106+ AccountCreationFormFieldView ( viewModel: . init( header: " Choose a password " ,
107+ placeholder: " Password " ,
108+ keyboardType: . default,
109+ text: . constant( " wwwwwwwwwwwwwwwwwwwwwwww " ) ,
110+ isSecure: true ,
111+ errorMessage: " Too simple " ,
112+ isFocused: false ) )
113+ . environment ( \. sizeCategory, . medium)
114+
115+ AccountCreationFormFieldView ( viewModel: . init( header: " Choose a password " ,
116+ placeholder: " Password " ,
117+ keyboardType: . default,
118+ text: . constant( " wwwwwwwwwwwwwwwwwwwwwwww " ) ,
119+ isSecure: true ,
120+ errorMessage: " Too simple " ,
121+ isFocused: false ) )
122+ . environment ( \. sizeCategory, . extraExtraExtraLarge)
123+ }
72124 }
73125}
0 commit comments