5
5
// Created by Kyle Van Essen on 11/14/19.
6
6
//
7
7
8
+ import Accessibility
8
9
import BlueprintUI
9
10
import ListableUI
10
11
import UIKit
@@ -38,6 +39,7 @@ import UIKit
38
39
/// ```
39
40
public struct ListReorderGesture : Element
40
41
{
42
+
41
43
public enum Begins {
42
44
case onTap
43
45
case onLongPress
@@ -54,9 +56,8 @@ public struct ListReorderGesture : Element
54
56
55
57
let actions : ReorderingActions
56
58
57
- /// The acccessibility Label of the item that will be reordered.
58
- /// This will be set as the gesture's accessibilityValue to provide a richer VoiceOver utterance.
59
- public var reorderItemAccessibilityLabel : String ? = nil
59
+ /// The acccessibility label for the reorder element. Defaults to "Reorder".
60
+ public var accessibilityLabel : String ?
60
61
61
62
/// Creates a new re-order gesture which wraps the provided element.
62
63
///
@@ -66,6 +67,7 @@ public struct ListReorderGesture : Element
66
67
isEnabled : Bool = true ,
67
68
actions : ReorderingActions ,
68
69
begins: Begins = . onTap,
70
+ accessibilityLabel: String ? = nil ,
69
71
wrapping element : Element
70
72
) {
71
73
self . isEnabled = isEnabled
@@ -74,6 +76,8 @@ public struct ListReorderGesture : Element
74
76
75
77
self . begins = begins
76
78
79
+ self . accessibilityLabel = accessibilityLabel
80
+
77
81
self . element = element
78
82
}
79
83
@@ -88,24 +92,16 @@ public struct ListReorderGesture : Element
88
92
public func backingViewDescription( with context: ViewDescriptionContext ) -> ViewDescription ?
89
93
{
90
94
return ViewDescription ( View . self) { config in
95
+
91
96
config. builder = {
92
97
View ( frame: context. bounds, wrapping: self )
93
98
}
99
+ config. contentView = { $0. containerView }
94
100
95
101
config. apply { view in
96
- view. isAccessibilityElement = true
97
- view. accessibilityLabel = ListableLocalizedStrings . ReorderGesture. accessibilityLabel
98
- view. accessibilityValue = reorderItemAccessibilityLabel
99
- view. accessibilityHint = ListableLocalizedStrings . ReorderGesture. accessibilityHint
100
- view. accessibilityTraits. formUnion ( . button)
101
- view. accessibilityCustomActions = accessibilityActions ( )
102
-
103
- view. recognizer. isEnabled = self . isEnabled
104
-
105
- view. recognizer. apply ( actions: self . actions)
106
-
107
- view. recognizer. minimumPressDuration = begins == . onLongPress ? 0.5 : 0.0
102
+ view. apply ( self )
108
103
}
104
+
109
105
}
110
106
}
111
107
@@ -118,9 +114,14 @@ public extension Element
118
114
func listReorderGesture(
119
115
with actions : ReorderingActions ,
120
116
isEnabled : Bool = true ,
121
- begins: ListReorderGesture . Begins = . onTap
117
+ begins: ListReorderGesture . Begins = . onTap,
118
+ accessibilityLabel: String ? = nil
122
119
) -> Element {
123
- ListReorderGesture ( isEnabled: isEnabled, actions: actions, begins: begins, wrapping: self )
120
+ ListReorderGesture ( isEnabled: isEnabled,
121
+ actions: actions,
122
+ begins: begins,
123
+ accessibilityLabel: accessibilityLabel,
124
+ wrapping: self )
124
125
}
125
126
}
126
127
@@ -129,25 +130,84 @@ fileprivate extension ListReorderGesture
129
130
{
130
131
private final class View : UIView
131
132
{
133
+
134
+ let containerView = UIView ( )
132
135
let recognizer : ItemReordering . GestureRecognizer
136
+ private lazy var proxyElement = UIAccessibilityElement ( accessibilityContainer: self )
137
+ private var minimumPressDuration : TimeInterval = 0.0 {
138
+ didSet {
139
+ updateGesturePressDuration ( )
140
+ }
141
+ }
142
+
143
+ @objc private func updateGesturePressDuration( ) {
144
+ self . recognizer. minimumPressDuration = UIAccessibility . isVoiceOverRunning ? 0.0 : self . minimumPressDuration
145
+ }
133
146
134
147
init ( frame: CGRect , wrapping : ListReorderGesture )
135
148
{
136
149
self . recognizer = . init( )
137
150
138
151
super. init ( frame: frame)
139
-
152
+ recognizer. accessibilityProxy = proxyElement
153
+ NotificationCenter . default. addObserver ( self , selector: #selector( updateGesturePressDuration) , name: UIAccessibility . voiceOverStatusDidChangeNotification, object: nil )
154
+
140
155
self . isOpaque = false
141
156
self . clipsToBounds = false
142
157
self . backgroundColor = . clear
143
158
144
159
self . addGestureRecognizer ( self . recognizer)
160
+
161
+ self . isAccessibilityElement = false
162
+
163
+ containerView. isOpaque = false
164
+ containerView. backgroundColor = . clear
165
+ addSubview ( containerView)
145
166
}
146
167
147
168
@available ( * , unavailable)
148
169
required init ? ( coder aDecoder: NSCoder ) {
149
170
listableInternalFatal ( )
150
171
}
172
+
173
+ func apply( _ model: ListReorderGesture ) {
174
+ proxyElement. accessibilityLabel = model. accessibilityLabel ?? ListableLocalizedStrings . ReorderGesture. accessibilityLabel
175
+ proxyElement. accessibilityHint = ListableLocalizedStrings . ReorderGesture. accessibilityHint
176
+ proxyElement. accessibilityTraits. formUnion ( . button)
177
+ proxyElement. accessibilityCustomActions = model. accessibilityActions ( )
178
+
179
+ recognizer. isEnabled = model. isEnabled
180
+
181
+ recognizer. apply ( actions: model. actions)
182
+ minimumPressDuration = model. begins == . onLongPress ? 0.5 : 0.0
183
+ }
184
+
185
+ override func layoutSubviews( ) {
186
+ super. layoutSubviews ( )
187
+ containerView. frame = bounds
188
+ }
189
+
190
+ override func hitTest( _ point: CGPoint , with event: UIEvent ? ) -> UIView ? {
191
+ if UIAccessibility . isVoiceOverRunning,
192
+ UIAccessibility . focusedElement ( using: . notificationVoiceOver) as? NSObject == proxyElement {
193
+ // Intercept touch events to avoid activating contained elements.
194
+ return self
195
+ }
196
+
197
+ return super. hitTest ( point, with: event)
198
+ }
199
+
200
+ override var accessibilityElements : [ Any ] ? {
201
+ get {
202
+ guard recognizer. isEnabled else { return super. accessibilityElements }
203
+ proxyElement. accessibilityFrame = self . accessibilityFrame
204
+ proxyElement. accessibilityActivationPoint = self . accessibilityActivationPoint
205
+ return [ containerView, proxyElement]
206
+ }
207
+ set {
208
+ fatalError ( " Cannot set accessibility elements directly " )
209
+ }
210
+ }
151
211
}
152
212
}
153
213
0 commit comments