@@ -29,6 +29,13 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
29
29
/// If nil - never hides on its own
30
30
var autohideIn : Double ?
31
31
32
+ /// Only allow dismiss by any means after this time passes
33
+ var dismissibleIn : Double ?
34
+
35
+ /// Becomes true when `dismissibleIn` times finishes
36
+ /// Makes no sense if `dismissibleIn` is nil
37
+ var dismissEnabled : Binding < Bool >
38
+
32
39
/// Should close on tap outside - default is `false`
33
40
var closeOnTapOutside : Bool
34
41
@@ -79,7 +86,10 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
79
86
private var itemRef : ClassReference < Binding < Item ? > > ?
80
87
81
88
/// holder for autohiding dispatch work (to be able to cancel it when needed)
82
- @State private var dispatchWorkHolder = DispatchWorkHolder ( )
89
+ @State private var autohidingWorkHolder = DispatchWorkHolder ( )
90
+
91
+ /// holder for `dismissibleIn` dispatch work (to be able to cancel it when needed)
92
+ @State private var dismissibleInWorkHolder = DispatchWorkHolder ( )
83
93
84
94
// MARK: - Autohide With Dragging
85
95
/// If user "grabbed" the popup to drag it around, put off the autohiding until he lifts his finger up
@@ -90,6 +100,10 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
90
100
/// if autohide time was set up, shows that timer has come to an end already
91
101
@State private var timeToHide = false
92
102
103
+ // MARK: - dismissibleIn
104
+
105
+ private var dismissEnabledRef : ClassReference < Binding < Bool > > ?
106
+
93
107
// MARK: - Internal
94
108
95
109
/// Set dismiss source to pass to dismiss callback
@@ -111,6 +125,8 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
111
125
112
126
self . params = params
113
127
self . autohideIn = params. autohideIn
128
+ self . dismissibleIn = params. dismissibleIn
129
+ self . dismissEnabled = params. dismissEnabled
114
130
self . closeOnTapOutside = params. closeOnTapOutside
115
131
self . backgroundColor = params. backgroundColor
116
132
self . backgroundView = params. backgroundView
@@ -127,6 +143,7 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
127
143
128
144
self . isPresentedRef = ClassReference ( self . $isPresented)
129
145
self . itemRef = ClassReference ( self . $item)
146
+ self . dismissEnabledRef = ClassReference ( self . dismissEnabled)
130
147
}
131
148
132
149
public func body( content: Content ) -> some View {
@@ -217,7 +234,8 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
217
234
dismissSource: $dismissSource,
218
235
backgroundColor: backgroundColor,
219
236
backgroundView: backgroundView,
220
- closeOnTapOutside: closeOnTapOutside
237
+ closeOnTapOutside: closeOnTapOutside,
238
+ dismissEnabled: dismissEnabled
221
239
)
222
240
. modifier ( getModifier ( ) )
223
241
}
@@ -236,7 +254,6 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
236
254
Popup (
237
255
params: params,
238
256
view: viewForItem != nil ? viewForItem! : view,
239
- popupPresented: popupPresented,
240
257
shouldShowContent: $shouldShowContent,
241
258
showContent: showContent,
242
259
isDragging: $isDragging,
@@ -251,6 +268,7 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
251
268
}
252
269
}
253
270
setupAutohide ( )
271
+ setupdismissibleIn ( )
254
272
}
255
273
} ,
256
274
dismissCallback: { source in
@@ -270,7 +288,8 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
270
288
} else {
271
289
closingIsInProcess = true
272
290
userWillDismissCallback ( dismissSource ?? . binding)
273
- dispatchWorkHolder. work? . cancel ( )
291
+ autohidingWorkHolder. work? . cancel ( )
292
+ dismissibleInWorkHolder. work? . cancel ( )
274
293
shouldShowContent = false // this will cause currentOffset change thus triggering the sliding hiding animation
275
294
animatableOpacity = 0
276
295
// do the rest once the animation is finished (see onAnimationCompleted())
@@ -289,6 +308,9 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
289
308
}
290
309
showContent = false // unload popup body after hiding animation is done
291
310
tempItemView = nil
311
+ if dismissibleIn != nil {
312
+ dismissEnabled. wrappedValue = false
313
+ }
292
314
performWithDelay ( 0.01 ) {
293
315
showSheet = false
294
316
}
@@ -302,27 +324,44 @@ public struct FullscreenPopup<Item: Equatable, PopupContent: View>: ViewModifier
302
324
func setupAutohide( ) {
303
325
// if needed, dispatch autohide and cancel previous one
304
326
if let autohideIn = autohideIn {
305
- dispatchWorkHolder . work? . cancel ( )
327
+ autohidingWorkHolder . work? . cancel ( )
306
328
307
329
// Weak reference to avoid the work item capturing the struct,
308
330
// which would create a retain cycle with the work holder itself.
309
331
310
- dispatchWorkHolder . work = DispatchWorkItem ( block: { [ weak isPresentedRef, weak itemRef] in
332
+ autohidingWorkHolder . work = DispatchWorkItem ( block: { [ weak isPresentedRef, weak itemRef] in
311
333
if isDragging {
312
334
timeToHide = true // raise this flag to hide the popup once the drag is over
313
335
return
314
336
}
315
337
dismissSource = . autohide
316
338
isPresentedRef? . value. wrappedValue = false
317
339
itemRef? . value. wrappedValue = nil
318
- dispatchWorkHolder . work = nil
340
+ autohidingWorkHolder . work = nil
319
341
} )
320
- if popupPresented, let work = dispatchWorkHolder . work {
342
+ if popupPresented, let work = autohidingWorkHolder . work {
321
343
DispatchQueue . main. asyncAfter ( deadline: . now( ) + autohideIn, execute: work)
322
344
}
323
345
}
324
346
}
325
347
348
+ func setupdismissibleIn( ) {
349
+ if let dismissibleIn = dismissibleIn {
350
+ dismissibleInWorkHolder. work? . cancel ( )
351
+
352
+ // Weak reference to avoid the work item capturing the struct,
353
+ // which would create a retain cycle with the work holder itself.
354
+
355
+ dismissibleInWorkHolder. work = DispatchWorkItem ( block: { [ weak dismissEnabledRef] in
356
+ dismissEnabledRef? . value. wrappedValue = true
357
+ dismissibleInWorkHolder. work = nil
358
+ } )
359
+ if popupPresented, let work = dismissibleInWorkHolder. work {
360
+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + dismissibleIn, execute: work)
361
+ }
362
+ }
363
+ }
364
+
326
365
func performWithDelay( _ delay: Double , block: @escaping ( ) -> ( ) ) {
327
366
DispatchQueue . main. asyncAfter ( deadline: . now( ) + delay) {
328
367
block ( )
0 commit comments