SwiftUI:程序化切换Sheet Detent时实现内容高度渐变动画
实现SwiftUI Sheet代码切换PresentationDetent时的渐进式高度动画
问题场景
当使用多PresentationDetent的Sheet时,两种操作的高度变化表现不一致:
- 拖拽Sheet顶部指示器调整时,视图高度会平滑渐进变化
- 通过代码直接修改
selectedDetent绑定值时,视图会瞬间跳转到目标高度
需要实现代码切换Detent时,也能达到和拖拽操作一致的渐进式高度过渡效果。
解决方案
核心思路是放弃直接修改系统预设Detent的绑定值,转而通过自定义Detent+动画过渡高度变量的方式,实现平滑的高度变化:
- 用
PresentationDetent.custom定义与系统预设高度匹配的自定义档位 - 维护动态高度状态变量,通过
withAnimation触发平滑过渡 - 同步高度变量与Detent的选中状态,保证Sheet行为一致性
修改后的代码示例
struct TestView: View { @State private var currentSheetHeight: CGFloat = UIScreen.main.bounds.height * 0.5 @State private var selectedDetent: PresentationDetent = .medium // 匹配系统默认的medium和large高度 private let mediumHeight = UIScreen.main.bounds.height * 0.5 private let largeHeight = UIScreen.main.bounds.height * 0.9 var body: some View { VStack {} .sheet(isPresented: .constant(true)) { Button { // 切换目标高度并应用动画 withAnimation(.easeInOut(duration: 0.3)) { currentSheetHeight = currentSheetHeight == mediumHeight ? largeHeight : mediumHeight } } label: { Text("\(Int(currentSheetHeight))") .frame(maxWidth: .infinity, maxHeight: .infinity) } .padding() .buttonStyle(.borderedProminent) .presentationDetents([ .custom(.init(id: "medium") { _ in self.mediumHeight }), .custom(.init(id: "large") { _ in self.largeHeight }) ], selection: $selectedDetent) .interactiveDismissDisabled() // 同步高度变化到Detent选中状态 .onChange(of: currentSheetHeight) { newHeight in selectedDetent = newHeight == mediumHeight ? .custom(.init(id: "medium") { _ in self.mediumHeight }) : .custom(.init(id: "large") { _ in self.largeHeight }) } } } }
代码说明
- 定义
mediumHeight和largeHeight对齐系统预设档位的高度,保证视觉效果一致 - 点击按钮时,用
withAnimation包裹高度变量的切换,触发UIKit级别的平滑过渡动画 - 使用自定义
PresentationDetent将高度值与Sheet档位绑定,替代系统预设的.medium/.large - 通过
onChange监听高度变化,同步更新selectedDetent,确保Sheet的交互状态与高度匹配
效果对比
| 拖拽调整Sheet | 代码切换Detent(带动画) |
|---|---|
![]() | ![]() |
内容的提问来源于stack exchange,提问作者Benoît
相关产品推荐
相关产品推荐



