SwiftUI自定义BottomSheet拖拽至屏幕底部附近自动关闭实现咨询
实现方案
我们采用动态读取内容高度设置关闭阈值的方案,适配任意外部传入的内容尺寸,具体修改如下:
核心修改点
- 新增
PreferenceKey用于读取BottomSheet内容的实际高度 - 新增存储内容高度的状态变量,在布局阶段读取传入内容的高度
- 调整拖拽手势逻辑,拖拽偏移超过内容高度的30%(可自定义)时自动关闭组件
完整修改后代码
import SwiftUI struct ContentView: View { @State private var isShow = false var body: some View { ZStack { Button( "Show Sheet", action: { self.isShow.toggle() } ) .zIndex(0) .allowsHitTesting(!isShow) BottomSheet( isShow: self.$isShow, content: { VStack { Text("A") Text("B") Text("C") Text("A") Text("B") Text("C") Text("A") Text("B") Text("C") } .frame( maxWidth: .infinity ) .background(Color(.yellow)) } ) .zIndex(1) } } } struct ScrimView: View { var body: some View { VStack {}.frame( maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom ) .background( Color(.gray) ) .opacity(0.5) } } // 新增PreferenceKey用于读取内容高度 private struct ContentHeightKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() } } struct BottomSheet<Content: View>: View { private let content: () -> Content @Binding var isShow: Bool @State var offsetY: CGFloat = 0 // 新增存储内容高度的状态变量 @State private var contentHeight: CGFloat = 0 init( isShow: Binding<Bool>, content: @escaping () -> Content ) { self._isShow = isShow self.content = content } var body: some View { ZStack(alignment: .bottom) { if self.isShow { ScrimView().zIndex( 0 ) .onTapGesture { self.isShow = false } .transition(.opacity) VStack { Button( "X", action: { self.isShow = false } ) self.content() // 新增:读取content的高度 .background( GeometryReader { geo in Color.clear.preference(key: ContentHeightKey.self, value: geo.size.height) } ) } .zIndex(1) .background(Color(.white)) .cornerRadius(10) .offset(y: self.offsetY) .gesture(self.gesture) .transition(.move(edge: .bottom)) // 新增:接收读取到的内容高度 .onPreferenceChange(ContentHeightKey.self) { height in contentHeight = height } } } .animation(.default, value: isShow) .onChange( of: self.isShow, perform: { value in self.offsetY = 0 } ) } var gesture: some Gesture { DragGesture().onChanged{ value in guard value.translation.height > 0 else { return } self.offsetY = value.translation.height } .onEnded{ value in guard value.translation.height > 0 else { return } // 拖拽偏移超过内容高度30%则关闭,阈值可自行调整 if value.translation.height > contentHeight * 0.3 { withAnimation { isShow = false } } else { withAnimation { offsetY = 0 } } } } }
可选方案(基于屏幕位置判断)
如果需要基于屏幕位置判断关闭阈值,可直接通过UIScreen.main.bounds.height获取屏幕高度,结合拖拽偏移量计算是否到达屏幕底部,不过该方案适配不同尺寸设备、安全区的成本更高,优先推荐使用内容高度判断的方案。
内容的提问来源于stack exchange,提问作者shingo.nakanishi
相关产品推荐
相关产品推荐

