如何在SwiftUI中实现类似Telegram的Sheet切换动画效果?
SwiftUI实现Telegram风格的Sheet无缝切换动画
原始代码
struct Services: View { @State private var isFirstSheetOpen = false @State private var isSecondSheetOpen = false var body: some View { Button("Open sheet") { isFirstSheetOpen.toggle() }.sheet(isPresented: $isFirstSheetOpen) { Button("Open second sheet") { isFirstSheetOpen.toggle() // 原代码存在笔误:应为isSecondSheetOpen.toggle() isFirstSecondOpen.toggle() }.sheet(isPresented: $isSecondSheetOpen) { Text("Second sheet") } } } }
问题描述
需要实现Telegram式Sheet切换效果:打开第二个Sheet时,第一个Sheet同步伴随动画关闭,过渡衔接自然。当前实现存在两个问题:
- 嵌套Sheet时,第一个Sheet关闭后会意外重新打开,导致第二个Sheet弹出时机混乱
- 并列添加两个Sheet时,切换无过渡动画;用
DispatchQueue延迟打开第二个Sheet,会导致动画衔接不流畅、耗时过长
解决方案
通过单状态变量管理Sheet显示状态,结合SwiftUI原生动画实现无缝过渡,避免嵌套/并列Sheet的问题:
- 定义枚举标记Sheet类型
enum ActiveSheet: Identifiable { case first, second var id: Self { self } }
- 完整实现代码
struct Services: View { enum ActiveSheet: Identifiable { case first, second var id: Self { self } } @State private var activeSheet: ActiveSheet? var body: some View { Button("Open sheet") { activeSheet = .first } // 使用sheet(item:)管理Sheet状态 .sheet(item: $activeSheet) { _ in switch activeSheet { case .first: FirstSheetView(activeSheet: $activeSheet) case .second: Text("Second sheet") case nil: EmptyView() } } // 统一设置过渡动画 .transition(.move(edge: .bottom).combined(with: .opacity)) .animation(.easeInOut(duration: 0.3), value: activeSheet) } } // 抽离第一个Sheet为独立视图 struct FirstSheetView: View { @Binding var activeSheet: Services.ActiveSheet? var body: some View { Button("Open second sheet") { // 用withAnimation触发同步过渡动画 withAnimation { activeSheet = .second } } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.white) } }
关键说明
- 使用
sheet(item:)替代isPresented,精准管理Sheet切换状态,彻底避免嵌套Sheet的异常问题 - 切换
activeSheet时用withAnimation包裹,让第一个Sheet的关闭动画与第二个Sheet的打开动画同步触发,实现无缝衔接 - 自定义过渡动画参数(如
duration)可调整动画速度,更贴近Telegram的效果
内容的提问来源于stack exchange,提问作者Roman Mahotskyi
相关产品推荐
相关产品推荐

