@@ -11,73 +11,59 @@ 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
+ . focused ( $focusedField, equals: . secure)
24
+ . keyboardType ( . default)
25
+ . writingToolsBehaviorDisabled ( )
26
+ . autocorrectionDisabled ( )
27
+ . textInputAutocapitalization ( . never)
28
+ . onAppear ( perform: model. onAppear)
29
+ . onReceive ( model. focusPublisher) { _ in
30
+ if shouldBecomeFirstResponder {
31
+ focusedField = . secure
32
+ }
40
33
}
41
- }
42
34
}
43
-
44
- init ( isSecured: Bool ,
45
- shouldBecomeFirstResponder: Bool ,
35
+
36
+ init ( shouldBecomeFirstResponder: Bool ,
46
37
text: Binding < String > ,
47
- onCommit: @escaping ( ) -> Void = { } ) {
48
- self . isSecured = isSecured
38
+ onCommit: @escaping ( ) -> Void = { }
39
+ ) {
49
40
self . shouldBecomeFirstResponder = shouldBecomeFirstResponder
50
41
self . text = text
51
42
self . onCommit = onCommit
52
43
}
53
-
54
- private func setFocus( for value: Bool ) {
55
- focusedField = value ? . secure : . plain
56
- }
57
44
}
58
45
59
46
60
47
private extension FocusableTextField {
61
48
enum Field : Hashable {
62
49
case secure
63
- case plain
64
50
}
65
51
}
66
52
67
53
fileprivate class FocusableTextFieldModel : ObservableObject {
68
54
var focusPublisher : PassthroughSubject < Void , Never > = . init( )
69
-
55
+
70
56
private var appearPublisher : CurrentValueSubject < Bool , Never > = . init( false )
71
57
private var activePublisher : CurrentValueSubject < Bool , Never > = . init( UIApplication . shared. isActive)
72
58
private var bag : Set < AnyCancellable > = . init( )
73
-
59
+
74
60
private var becomeActivePublisher : AnyPublisher < Void , Never > {
75
61
NotificationCenter . default
76
62
. publisher ( for: UIApplication . didBecomeActiveNotification)
77
63
. map { _ in ( ) }
78
64
. eraseToAnyPublisher ( )
79
65
}
80
-
66
+
81
67
/// This is the minimum allowable delay, calculated empirically for all iOS versions prior 16.
82
68
private var appearDelay : Int {
83
69
if #available( iOS 16 . 0 , * ) {
@@ -86,22 +72,22 @@ fileprivate class FocusableTextFieldModel: ObservableObject {
86
72
return 500
87
73
}
88
74
}
89
-
75
+
90
76
init ( ) {
91
77
bind ( )
92
78
}
93
-
79
+
94
80
func onAppear( ) {
95
81
appearPublisher. send ( true )
96
82
}
97
-
83
+
98
84
private func bind( ) {
99
85
becomeActivePublisher
100
86
. sink { [ weak self] _ in
101
87
self ? . activePublisher. send ( true )
102
88
}
103
89
. store ( in: & bag)
104
-
90
+
105
91
appearPublisher
106
92
. filter { $0 }
107
93
. delay ( for: . milliseconds( appearDelay) , scheduler: DispatchQueue . main)
@@ -113,9 +99,19 @@ fileprivate class FocusableTextFieldModel: ObservableObject {
113
99
}
114
100
}
115
101
116
-
117
102
fileprivate extension UIApplication {
118
103
var isActive : Bool {
119
104
applicationState == . active
120
105
}
121
106
}
107
+
108
+ fileprivate extension View {
109
+ @ViewBuilder
110
+ func writingToolsBehaviorDisabled( ) -> some View {
111
+ if #available( iOS 18 . 0 , * ) {
112
+ self . writingToolsBehavior ( . disabled)
113
+ } else {
114
+ self
115
+ }
116
+ }
117
+ }
0 commit comments