4
4
// Created by Zac White on 2/14/23.
5
5
//
6
6
7
+ import Combine
7
8
import SwiftUI
8
9
9
10
struct AnyDestination : Equatable {
@@ -24,20 +25,55 @@ class DestinationLookup: ObservableObject {
24
25
@Published var table : [ String : AnyDestination ] = [ : ]
25
26
}
26
27
27
- // Tracks Z index for accessibility
28
- class FlowDepth : ObservableObject {
28
+ class AccessibilityManager : ObservableObject {
29
29
@Published var zIndex : Double = 0.0
30
+ var skrimIndex : Double { zIndex - 0.1 }
31
+ var behindSkrim : Double { zIndex - 0.2 }
32
+
33
+ // Setup VoiceOver Observer
34
+ @Published var isVoiceOverRunning : Bool = UIAccessibility . isVoiceOverRunning
35
+ init ( ) {
36
+ NotificationCenter . default
37
+ . publisher ( for: UIAccessibility . voiceOverStatusDidChangeNotification)
38
+ . map { _ in UIAccessibility . isVoiceOverRunning }
39
+ . assign ( to: & $isVoiceOverRunning)
40
+ }
41
+
42
+ func setIndex( _ input: Int ) { self . zIndex = Double ( input + 1 ) }
43
+
44
+ func decrementIndex( ) { self . zIndex -= 1.0 }
45
+
46
+ func calcSkrim( ) -> Double {
47
+ if isVoiceOverRunning { return skrimIndex }
48
+ return zIndex > 1.0 ? zIndex : skrimIndex
49
+ }
50
+ }
51
+
52
+ struct AccessibilityModifier : ViewModifier {
53
+ @EnvironmentObject var accessibilityManager : AccessibilityManager
54
+ var isHidden : ( Double , Int ) -> Bool = { d, i in d != Double ( i + 1 ) }
55
+ let element : Int
56
+
57
+ func body( content: Content ) -> some View {
58
+ content
59
+ . environment ( \. flowDepth, element + 1 )
60
+ . zIndex ( Double ( accessibilityManager. zIndex) )
61
+ . accessibilityElement ( children: . contain)
62
+ . accessibilityHidden ( isHidden ( accessibilityManager. zIndex, element) )
63
+ . onAppear { accessibilityManager. setIndex ( element) }
64
+ }
30
65
}
31
66
32
67
struct FlowDestinationModifier < D: Hashable > : ViewModifier {
33
68
@State var dataType : D . Type
34
69
@State var destination : AnyDestination
35
70
@EnvironmentObject var destinationLookup : DestinationLookup
36
- @EnvironmentObject var flowDepth : FlowDepth
71
+ @EnvironmentObject var accessibilityManager : AccessibilityManager
72
+ @Environment ( \. flowDismiss) var flowDismiss
37
73
38
74
func body( content: Content ) -> some View {
39
75
content
40
- . zIndex ( flowDepth . zIndex)
76
+ . zIndex ( accessibilityManager . isVoiceOverRunning ? accessibilityManager . zIndex : accessibilityManager . behindSkrim )
41
77
// swiftlint:disable:next force_unwrapping
42
78
. onAppear { destinationLookup. table. merge ( [ _mangledTypeName ( dataType) !: destination] , uniquingKeysWith: { _, rhs in rhs } ) }
43
79
}
@@ -91,14 +127,12 @@ public extension View {
91
127
guard let param = AnyDestination . cast ( data: param, to: type) else {
92
128
fatalError ( )
93
129
}
94
-
95
130
return AnyView (
96
131
destination ( param)
97
132
. accessibilityElement ( children: . contain)
98
133
. accessibilityRespondsToUserInteraction ( true )
99
134
)
100
135
} )
101
-
102
136
return modifier ( FlowDestinationModifier ( dataType: type, destination: destination) )
103
137
}
104
138
}
@@ -186,7 +220,7 @@ public struct FlowStack<Root: View, Overlay: View>: View {
186
220
private var usesInternalPath : Bool = false
187
221
188
222
@State private var destinationLookup : DestinationLookup = . init( )
189
- @StateObject var flowDepth : FlowDepth = . init( )
223
+ @StateObject var accessibilityManager : AccessibilityManager = . init( )
190
224
191
225
/// Creates a flow stack that manages its own navigation state.
192
226
/// - Parameters:
@@ -231,14 +265,14 @@ public struct FlowStack<Root: View, Overlay: View>: View {
231
265
private func skrim( for element: FlowElement ) -> some View {
232
266
if element == pathToUse. wrappedValue. elements. last, element. context? . shouldShowSkrim == true {
233
267
Rectangle ( )
234
- . foregroundColor ( Color . black. opacity ( 0.7 ) )
235
- . transition ( . opacity)
236
- . ignoresSafeArea ( )
237
- . zIndex ( flowDepth . zIndex - 0.1 )
238
- . id ( element. hashValue)
239
- . onTapGesture {
240
- flowDismissAction ( )
241
- }
268
+ . foregroundColor ( Color . black. opacity ( 0.7 ) )
269
+ . transition ( . opacity)
270
+ . ignoresSafeArea ( )
271
+ . zIndex ( accessibilityManager . calcSkrim ( ) )
272
+ . id ( element. hashValue)
273
+ . onTapGesture {
274
+ flowDismissAction ( )
275
+ }
242
276
}
243
277
}
244
278
@@ -250,9 +284,9 @@ public struct FlowStack<Root: View, Overlay: View>: View {
250
284
FlowDismissAction (
251
285
onDismiss: {
252
286
withTransaction ( transaction) {
287
+ accessibilityManager. decrementIndex ( )
253
288
pathToUse. wrappedValue. removeLast ( )
254
289
}
255
- flowDepth. zIndex -= 1
256
290
} )
257
291
}
258
292
@@ -267,35 +301,32 @@ public struct FlowStack<Root: View, Overlay: View>: View {
267
301
root ( )
268
302
. contentShape ( Rectangle ( ) )
269
303
. accessibilityElement ( children: . contain)
270
- . accessibilityHidden ( flowDepth . zIndex != 0 )
271
-
304
+ . accessibilityHidden ( accessibilityManager . zIndex != 0 )
305
+ . environment ( \ . flowDepth , 0 )
272
306
273
307
ForEach ( pathToUse. wrappedValue. elements, id: \. self) { element in
274
308
if let destination = destination ( for: element. value) {
275
309
276
310
skrim ( for: element)
311
+
277
312
destination. content ( element. value)
278
313
. contentShape ( Rectangle ( ) )
279
314
. frame ( maxWidth: . infinity, maxHeight: . infinity)
280
315
. id ( element. hashValue)
281
316
. transition ( . flowTransition( with: element. context ?? . init( ) ) )
282
- . zIndex ( flowDepth. zIndex)
283
- . accessibilityElement ( children: . contain)
284
- . accessibilityHidden ( flowDepth. zIndex != Double ( element. index + 1 ) )
285
- . onAppear { flowDepth. zIndex = Double ( element. index + 1 ) }
317
+ . modifier ( AccessibilityModifier ( element: element. index) )
286
318
}
287
319
}
288
320
}
289
- . zIndex ( flowDepth. zIndex)
290
321
. accessibilityElement ( children: . contain)
291
322
. overlay ( alignment: overlayAlignment) {
292
323
overlay ( )
293
- . zIndex ( flowDepth . zIndex - 1.0 )
324
+ . environment ( \ . flowDepth , - 1 )
294
325
}
295
326
. environment ( \. flowPath, pathToUse)
296
327
. environment ( \. flowTransaction, transaction)
297
328
. environmentObject ( destinationLookup)
298
- . environmentObject ( flowDepth )
329
+ . environmentObject ( accessibilityManager )
299
330
. environment ( \. flowDismiss, flowDismissAction)
300
331
}
301
332
}
0 commit comments