Skip to content

Commit 48ba51d

Browse files
authored
Feature: Swipe Up Dismiss (#45)
* Added buffer for dismiss * Added parameter * Disable scroll while swiping to to dismiss. Previously was doing scrolling up and swipe up to dismiss.
1 parent 8225781 commit 48ba51d

4 files changed

Lines changed: 31 additions & 12 deletions

File tree

Sources/FlowStack/FlowLink.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ public struct FlowLink<Label>: View where Label: View {
217217
/// - shadowColor: The shadow color applied to the transitioning destination view. This value should typically match the shadow color of the flow link contents or flow link animation anchor for visual consistency.
218218
/// - shadowOffset: The shadow offset applied to the transitioning destination view. This value should typically match the shadow offset of the flow link contents or flow link animation anchor for visual consistency.
219219
/// - zoomStyle: The zoom style applied to the transitioning destination view
220-
public init(animateFromAnchor: Bool = true, transitionFromSnapshot: Bool = true, retakeSnapshots: Bool = false, cornerRadius: CGFloat = 0, cornerStyle: RoundedCornerStyle = .circular, shadowRadius: CGFloat = 0, shadowColor: Color? = nil, shadowOffset: CGPoint = .zero, zoomStyle: ZoomStyle = .scaleHorizontally) {
220+
/// - swipeUpToDismiss: Whether the destination view should allow swipe up to dismiss
221+
public init(animateFromAnchor: Bool = true, transitionFromSnapshot: Bool = true, retakeSnapshots: Bool = false, cornerRadius: CGFloat = 0, cornerStyle: RoundedCornerStyle = .circular, shadowRadius: CGFloat = 0, shadowColor: Color? = nil, shadowOffset: CGPoint = .zero, zoomStyle: ZoomStyle = .scaleHorizontally, swipeUpToDismiss: Bool = false) {
221222
self.animateFromAnchor = animateFromAnchor
222223
self.transitionFromSnapshot = transitionFromSnapshot
223224
self.retakeSnapshots = retakeSnapshots
@@ -227,6 +228,7 @@ public struct FlowLink<Label>: View where Label: View {
227228
self.shadowColor = shadowColor
228229
self.shadowOffset = shadowOffset
229230
self.zoomStyle = zoomStyle
231+
self.swipeUpToDismiss = swipeUpToDismiss
230232
}
231233

232234
let animateFromAnchor: Bool
@@ -242,6 +244,8 @@ public struct FlowLink<Label>: View where Label: View {
242244

243245
let showsSkrim: Bool = true
244246
let zoomStyle: ZoomStyle
247+
248+
let swipeUpToDismiss: Bool
245249
}
246250

247251
public enum Activation { case overlayButton, tapGesture }
@@ -460,7 +464,8 @@ public struct FlowLink<Label>: View where Label: View {
460464
shadowColor: configuration.shadowColor,
461465
shadowOffset: configuration.shadowOffset,
462466
shouldShowSkrim: configuration.showsSkrim,
463-
shouldScaleHorizontally: configuration.zoomStyle == .scaleHorizontally
467+
shouldScaleHorizontally: configuration.zoomStyle == .scaleHorizontally,
468+
swipeUpToDismiss: configuration.swipeUpToDismiss
464469
)
465470
})
466471
.onPreferenceChange(PathContextKey.self) { value in

Sources/FlowStack/FlowPath.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ struct PathContext: Equatable, Hashable {
4040

4141
var shouldShowSkrim: Bool = true
4242
var shouldScaleHorizontally: Bool = true
43+
44+
var swipeUpToDismiss: Bool = false
4345
}
4446

4547
struct FlowElement: Equatable, Hashable {

Sources/FlowStack/FlowTransition.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ extension AnyTransition {
162162
let scaleRatio = context.shouldScaleHorizontally ? zoomRect.size.width / proxy.size.width : 1.0
163163

164164
content
165-
.onInteractiveDismissGesture(threshold: 80, isEnabled: !isDisabled, isDismissing: isDismissing, onDismiss: {
165+
.onInteractiveDismissGesture(threshold: 80, isEnabled: !isDisabled, isDismissing: isDismissing, swipeUpToDismiss: context.swipeUpToDismiss, onDismiss: {
166166
dismiss()
167167
isDismissing = true
168168
}, onPan: { offset in

Sources/FlowStack/View+InteractiveDismiss.swift

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ struct InteractiveDismissContainer<T: View>: UIViewControllerRepresentable {
4444
var onPan: (CGPoint) -> Void
4545
var isEnabled: Bool
4646
var isDismissing: Bool
47+
48+
var swipeUpToDismiss: Bool
49+
4750
var onDismiss: () -> Void
4851
var onEnded: (Bool) -> Void
4952

@@ -59,7 +62,7 @@ struct InteractiveDismissContainer<T: View>: UIViewControllerRepresentable {
5962
}
6063

6164
func makeCoordinator() -> InteractiveDismissCoordinator {
62-
InteractiveDismissCoordinator(threshold: threshold, onPan: onPan, isEnabled: isEnabled, isDismissing: isDismissing, onDismiss: onDismiss, onEnded: onEnded)
65+
InteractiveDismissCoordinator(threshold: threshold, onPan: onPan, isEnabled: isEnabled, isDismissing: isDismissing, swipeUpToDismiss: swipeUpToDismiss, onDismiss: onDismiss, onEnded: onEnded)
6366
}
6467
}
6568

@@ -136,6 +139,9 @@ class InteractiveDismissCoordinator: NSObject, ObservableObject, UIGestureRecogn
136139
handleDismiss()
137140
}
138141
}
142+
143+
var swipeUpToDismiss: Bool
144+
139145
var onDismiss: () -> Void
140146
var onEnded: (Bool) -> Void
141147

@@ -171,12 +177,13 @@ class InteractiveDismissCoordinator: NSObject, ObservableObject, UIGestureRecogn
171177
}
172178
}
173179

174-
init(threshold: Double, onPan: @escaping (CGPoint) -> Void, isEnabled: Bool, isDismissing: Bool, onDismiss: @escaping () -> Void, onEnded: @escaping (Bool) -> Void) {
180+
init(threshold: Double, onPan: @escaping (CGPoint) -> Void, isEnabled: Bool, isDismissing: Bool, swipeUpToDismiss: Bool, onDismiss: @escaping () -> Void, onEnded: @escaping (Bool) -> Void) {
175181
self.threshold = threshold
176182

177183
self.onPan = onPan
178184
self.isEnabled = isEnabled
179185
self.isDismissing = isDismissing
186+
self.swipeUpToDismiss = swipeUpToDismiss
180187
self.onDismiss = onDismiss
181188
self.onEnded = onEnded
182189

@@ -215,7 +222,7 @@ class InteractiveDismissCoordinator: NSObject, ObservableObject, UIGestureRecogn
215222
isUpdating = true
216223
onPan(offset)
217224

218-
let shouldDismiss = offset.y > threshold || (offset.x > threshold && isEdge)
225+
let shouldDismiss = offset.y > threshold || (offset.x > threshold && isEdge) || (-offset.y > threshold * 2 && swipeUpToDismiss)
219226
if shouldDismiss != isPastThreshold && shouldDismiss {
220227
impactGenerator.impactOccurred()
221228
}
@@ -237,9 +244,13 @@ class InteractiveDismissCoordinator: NSObject, ObservableObject, UIGestureRecogn
237244
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
238245
guard gestureRecognizer == panGestureRecognizer, let scrollView = scrollView else { return true }
239246

240-
guard panGestureRecognizer.translation(in: scrollView).y > 0 else { return false }
241-
242-
return scrollView.contentOffset.y - 5 <= -scrollView.contentInset.top
247+
if panGestureRecognizer.translation(in: scrollView).y > 0 {
248+
return scrollView.contentOffset.y - 5 <= -scrollView.contentInset.top
249+
} else {
250+
let belowBounds = scrollView.contentOffset.y + UIScreen.main.bounds.height > scrollView.contentSize.height + 20 && swipeUpToDismiss
251+
scrollView.isScrollEnabled = !belowBounds
252+
return belowBounds
253+
}
243254
}
244255

245256
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
@@ -248,6 +259,8 @@ class InteractiveDismissCoordinator: NSObject, ObservableObject, UIGestureRecogn
248259
return true
249260
}
250261

262+
scrollView.isScrollEnabled = true
263+
251264
if gestureRecognizer == panGestureRecognizer && otherGestureRecognizer == edgeGestureRecognizer {
252265
return false
253266
}
@@ -263,7 +276,6 @@ class InteractiveDismissCoordinator: NSObject, ObservableObject, UIGestureRecogn
263276
otherGestureRecognizer.isEnabled = false
264277
otherGestureRecognizer.isEnabled = true
265278
}
266-
267279
return true
268280
}
269281

@@ -278,7 +290,7 @@ class InteractiveDismissCoordinator: NSObject, ObservableObject, UIGestureRecogn
278290
}
279291

280292
extension View {
281-
func onInteractiveDismissGesture(threshold: Double = 50, isEnabled: Bool = true, isDismissing: Bool = false, onDismiss: @escaping () -> Void, onPan: @escaping (CGPoint) -> Void = { _ in }, onEnded: @escaping (Bool) -> Void = { _ in }) -> some View {
282-
InteractiveDismissContainer(threshold: threshold, onPan: onPan, isEnabled: isEnabled, isDismissing: isDismissing, onDismiss: onDismiss, onEnded: onEnded, content: self)
293+
func onInteractiveDismissGesture(threshold: Double = 50, isEnabled: Bool = true, isDismissing: Bool = false, swipeUpToDismiss: Bool, onDismiss: @escaping () -> Void, onPan: @escaping (CGPoint) -> Void = { _ in }, onEnded: @escaping (Bool) -> Void = { _ in }) -> some View {
294+
InteractiveDismissContainer(threshold: threshold, onPan: onPan, isEnabled: isEnabled, isDismissing: isDismissing, swipeUpToDismiss: swipeUpToDismiss, onDismiss: onDismiss, onEnded: onEnded, content: self)
283295
}
284296
}

0 commit comments

Comments
 (0)