@@ -11,73 +11,57 @@ import SwiftUI
11
11
import Combine
12
12
13
13
struct FocusableTextField : View {
14
- let isSecured : Bool
15
14
let shouldBecomeFirstResponder : Bool
16
15
let text : Binding < String >
17
16
var onCommit : ( ) -> Void = { }
18
-
17
+
19
18
@FocusState private var focusedField : Field ?
20
19
@StateObject private var model : FocusableTextFieldModel = . init( )
21
-
20
+
22
21
var body : some View {
23
- ZStack {
24
- if isSecured {
25
- SecureField ( " " , text: text, onCommit: onCommit)
26
- . focused ( $focusedField, equals: . secure)
27
- } else {
28
- TextField ( " " , text: text, onCommit: onCommit)
29
- . focused ( $focusedField, equals: . plain)
30
- }
31
- }
32
- . keyboardType ( . default)
33
- . onAppear ( perform: model. onAppear)
34
- . onChange ( of: isSecured) { newValue in
35
- setFocus ( for: newValue)
36
- }
37
- . onReceive ( model. focusPublisher) { _ in
38
- if shouldBecomeFirstResponder {
39
- setFocus ( for: isSecured)
22
+ SecureField ( " " , text: text, onCommit: onCommit)
23
+ . textContentType ( . oneTimeCode) // to prevent passwords suggestion. Tested on ios 15-18
24
+ . focused ( $focusedField, equals: . secure)
25
+ . keyboardType ( . asciiCapable)
26
+ . onAppear ( perform: model. onAppear)
27
+ . onReceive ( model. focusPublisher) { _ in
28
+ if shouldBecomeFirstResponder {
29
+ focusedField = . secure
30
+ }
40
31
}
41
- }
42
32
}
43
-
44
- init ( isSecured: Bool ,
45
- shouldBecomeFirstResponder: Bool ,
33
+
34
+ init ( shouldBecomeFirstResponder: Bool ,
46
35
text: Binding < String > ,
47
- onCommit: @escaping ( ) -> Void = { } ) {
48
- self . isSecured = isSecured
36
+ onCommit: @escaping ( ) -> Void = { }
37
+ ) {
49
38
self . shouldBecomeFirstResponder = shouldBecomeFirstResponder
50
39
self . text = text
51
40
self . onCommit = onCommit
52
41
}
53
-
54
- private func setFocus( for value: Bool ) {
55
- focusedField = value ? . secure : . plain
56
- }
57
42
}
58
43
59
44
60
45
private extension FocusableTextField {
61
46
enum Field : Hashable {
62
47
case secure
63
- case plain
64
48
}
65
49
}
66
50
67
51
fileprivate class FocusableTextFieldModel : ObservableObject {
68
52
var focusPublisher : PassthroughSubject < Void , Never > = . init( )
69
-
53
+
70
54
private var appearPublisher : CurrentValueSubject < Bool , Never > = . init( false )
71
55
private var activePublisher : CurrentValueSubject < Bool , Never > = . init( UIApplication . shared. isActive)
72
56
private var bag : Set < AnyCancellable > = . init( )
73
-
57
+
74
58
private var becomeActivePublisher : AnyPublisher < Void , Never > {
75
59
NotificationCenter . default
76
60
. publisher ( for: UIApplication . didBecomeActiveNotification)
77
61
. map { _ in ( ) }
78
62
. eraseToAnyPublisher ( )
79
63
}
80
-
64
+
81
65
/// This is the minimum allowable delay, calculated empirically for all iOS versions prior 16.
82
66
private var appearDelay : Int {
83
67
if #available( iOS 16 . 0 , * ) {
@@ -86,22 +70,22 @@ fileprivate class FocusableTextFieldModel: ObservableObject {
86
70
return 500
87
71
}
88
72
}
89
-
73
+
90
74
init ( ) {
91
75
bind ( )
92
76
}
93
-
77
+
94
78
func onAppear( ) {
95
79
appearPublisher. send ( true )
96
80
}
97
-
81
+
98
82
private func bind( ) {
99
83
becomeActivePublisher
100
84
. sink { [ weak self] _ in
101
85
self ? . activePublisher. send ( true )
102
86
}
103
87
. store ( in: & bag)
104
-
88
+
105
89
appearPublisher
106
90
. filter { $0 }
107
91
. delay ( for: . milliseconds( appearDelay) , scheduler: DispatchQueue . main)
0 commit comments