@@ -35,6 +35,7 @@ struct VerificationCodeInputField: View {
3535
3636 @State private var digitFields : [ String ] = [ ]
3737 @State private var focusedIndex : Int ? = nil
38+ @State private var pendingInternalCodeUpdates = 0
3839
3940 var body : some View {
4041 VStack ( spacing: 8 ) {
@@ -45,6 +46,8 @@ struct VerificationCodeInputField: View {
4546 isError: isError,
4647 isFocused: focusedIndex == index,
4748 maxDigits: codeLength - index,
49+ position: index + 1 ,
50+ totalDigits: codeLength,
4851 onDigitChanged: { newDigit in
4952 handleDigitChanged ( at: index, newDigit: newDigit)
5053 } ,
@@ -75,21 +78,56 @@ struct VerificationCodeInputField: View {
7578 }
7679 . onAppear {
7780 // Initialize digit fields from the code binding
78- updateDigitFieldsFromCode ( )
81+ updateDigitFieldsFromCode ( shouldUpdateFocus: true , forceFocus: true )
82+ }
83+ . onChange ( of: code) { _ in
84+ if pendingInternalCodeUpdates > 0 {
85+ pendingInternalCodeUpdates -= 1
86+ return
87+ }
88+ updateDigitFieldsFromCode ( shouldUpdateFocus: true )
7989 }
8090 }
8191
82- private func updateDigitFieldsFromCode( ) {
83- let codeArray = Array ( code)
84- for i in 0 ..< codeLength {
85- if i < codeArray. count {
86- digitFields [ i] = String ( codeArray [ i] )
87- } else {
88- digitFields [ i] = " "
92+ private func updateDigitFieldsFromCode( shouldUpdateFocus: Bool , forceFocus: Bool = false ) {
93+ let sanitized = code. filter { $0. isNumber }
94+ let truncated = String ( sanitized. prefix ( codeLength) )
95+ var newFields = Array ( repeating: " " , count: codeLength)
96+
97+ for (offset, character) in truncated. enumerated ( ) {
98+ newFields [ offset] = String ( character)
99+ }
100+
101+ let fieldsChanged = newFields != digitFields
102+ if fieldsChanged {
103+ digitFields = newFields
104+ }
105+
106+ if code != truncated {
107+ commitCodeChange ( truncated)
108+ }
109+
110+ if shouldUpdateFocus && ( fieldsChanged || forceFocus) {
111+ let newFocus = truncated. count < codeLength ? truncated. count : nil
112+ DispatchQueue . main. async {
113+ focusedIndex = newFocus
114+ }
115+ }
116+
117+ if fieldsChanged && truncated. count == codeLength {
118+ DispatchQueue . main. async {
119+ onCodeComplete ( truncated)
89120 }
90121 }
91122 }
92-
123+
124+ private func commitCodeChange( _ newCode: String ) {
125+ if code != newCode {
126+ pendingInternalCodeUpdates += 1
127+ code = newCode
128+ }
129+ }
130+
93131 private func handleDigitChanged( at index: Int , newDigit: String ) {
94132 let sanitized = newDigit. filter { $0. isNumber }
95133
@@ -117,7 +155,7 @@ struct VerificationCodeInputField: View {
117155 }
118156
119157 let newCode = digitFields. joined ( )
120- code = newCode
158+ commitCodeChange ( newCode)
121159 onCodeChange ( newCode)
122160
123161 if !digit. isEmpty,
@@ -154,7 +192,7 @@ struct VerificationCodeInputField: View {
154192
155193 // Update the main code string
156194 let newCode = digitFields. joined ( )
157- code = newCode
195+ commitCodeChange ( newCode)
158196 onCodeChange ( newCode)
159197 }
160198
@@ -174,7 +212,7 @@ struct VerificationCodeInputField: View {
174212 }
175213
176214 let newCode = updatedFields. joined ( )
177- code = newCode
215+ commitCodeChange ( newCode)
178216 onCodeChange ( newCode)
179217
180218 if newCode. count == codeLength {
@@ -215,6 +253,8 @@ private struct SingleDigitField: View {
215253 let isError : Bool
216254 let isFocused : Bool
217255 let maxDigits : Int
256+ let position : Int
257+ let totalDigits : Int
218258 let onDigitChanged : ( String ) -> Void
219259 let onBackspace : ( ) -> Void
220260 let onFocusChanged : ( Bool ) -> Void
@@ -268,6 +308,10 @@ private struct SingleDigitField: View {
268308 )
269309 )
270310 . frame ( maxWidth: . infinity)
311+ . accessibilityElement ( children: . ignore)
312+ . accessibilityLabel ( " Digit \( position) of \( totalDigits) " )
313+ . accessibilityValue ( digit. isEmpty ? " Empty " : digit)
314+ . accessibilityHint ( " Enter verification code digit " )
271315 }
272316}
273317
0 commit comments