I have an expandable bottom sheet that includes content inside of a scroll view. When I am sliding it up, I disable the scroll view and adjust the offset of the modal sheet via a DragGesture() and only enable the scroll view when the container modal is fully presented.
The problem comes up when I'm trying to swipe the modal down to dismiss it. If I start the swipe while the scroll view content is fully scrolled to the top, it "catches" and triggers the Apple sheet underneath (that is currently presenting my custom bottom sheet) to start the interactive dismissal.
Does anyone know of any way to specify that a particular scroll view NOT be allowed to trigger interactive dismissal, or any other potential methods around it?
Reference Code.
struct ExpandableBottomSheet: ViewModifier {
enum ViewState {
case full
case half
}
@GestureState private var dragState = DragState.inactive
@State private var positionOffset: CGFloat = 0.0
@State private var viewState = ViewState.half
@State private var scrollOffset: CGFloat = 0.0
@Binding var isShow: Bool
func body(content: Content) -> some View {
GeometryReader { geometry in
VStack {
Spacer()
HandleBar()
ScrollView(.vertical) {
GeometryReader { scrollViewProxy in
Color.clear.preference(key: ScrollOffsetKey.self, value: scrollViewProxy.frame(in: .named("scrollview")).minY)
}
.frame(height: 0)
content
.offset(y: -self.scrollOffset)
.animation(nil)
}
.background(Color(.systemBackground))
.cornerRadius(10, antialiased: true)
.disabled(self.viewState == .half)
.coordinateSpace(name: "scrollview")
}
.offset(y: geometry.size.height/2 + self.dragState.translation.height + self.positionOffset)
.offset(y: self.scrollOffset)
.animation(.interpolatingSpring(stiffness: 200.0, damping: 25.0, initialVelocity: 10.0))
.edgesIgnoringSafeArea(.all)
.onPreferenceChange(ScrollOffsetKey.self) { value in
print("Preference ScrollOffsetKey Changed")
if self.viewState == .full {
self.scrollOffset = value > 0 ? value : 0
if self.scrollOffset > 120 {
self.positionOffset = 0
self.viewState = .half
self.scrollOffset = 0
}
}
}
.gesture(DragGesture()
.updating(self.$dragState, body: { (value, state, transaction) in
print("Updating DragGesture: (value.translation)")
state = .dragging(translation: value.translation)
})
.onEnded({ (value) in
if self.viewState == .half {
// Threshold #1
// Slide up and when it goes beyond the threshold
// change the view state to fully opened by updating
// the position offset
if value.translation.height < -geometry.size.height * 0.25 {
self.positionOffset = -geometry.size.height/2 + 50
self.viewState = .full
}
// Threshold #2
// Slide down and when it goes pass the threshold
// dismiss the view by setting isShow to false
if value.translation.height > geometry.size.height * 0.3 {
self.isShow = false
}
}
})
)
}
}
}
struct ScrollOffsetKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue = CGFloat.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
enum DragState {
case inactive
case pressing
case dragging(translation: CGSize)
var translation: CGSize {
switch self {
case .inactive, .pressing:
return .zero
case .dragging(let translation):
return translation
}
}
var isDragging: Bool {
switch self {
case .pressing, .dragging:
return true
case .inactive:
return false
}
}
}
question from:
https://stackoverflow.com/questions/66057259/is-there-a-way-to-disable-scrollview-from-triggering-a-sheets-interactive-dismis 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…