SwiftUI中Sheet内用户详情动画实现崩溃问题求助
SwiftUI Sheet使用Matched Geometry时崩溃问题及修复方案
我尝试在SwiftUI中创建包含用户详情的Sheet,期望展开Sheet时详情内容通过动画左对齐。但使用matched geometry时出现崩溃,报错信息为:com.apple.SwiftUI.AsyncRenderer (11): EXC_BREAKPOINT (code=1, subcode=0x1a69151cc)
当前实现逻辑:通过isProfileExpanded变量控制Sheet内布局,监听Sheet尺寸变化切换该变量状态,核心代码如下:
struct ProfileListView: View { @State var navigateToActivityDetail: Bool = false var body: some View { VStack { } .sheet(isPresented: $navigateToActivityDetail) { ProfileDetailDetailView() .presentationDetents([.medium, .large, .fraction(0.75)]) .background(Color(ColorConstants.GeneralElevation1).edgesIgnoringSafeArea(.all)) } } }
GeometryReader { proxy in VStack(spacing: 32) { VStack { DrawerStateUser(isProfileExpanded: $isProfileExpanded) } } .onChange(of: proxy.size.height, perform: { newValue in if newValue > (screenSize().size.height*0.6) { withAnimation(.easeInOut) { self.isProfileExpanded = false } } else { withAnimation(.easeInOut) { self.isProfileExpanded = true } } }) .padding(.top, 40) }
效果示意图:

崩溃原因分析
- 尺寸监听频繁触发冲突:Sheet尺寸变化是连续动画过程,
proxy.size.height会反复触发onChange回调,多次修改isProfileExpanded状态,导致Matched Geometry动画时序错乱。 - Matched Geometry上下文缺失:未正确配置共享命名空间(Namespace),或状态切换时视图被销毁/重建,引发布局树不稳定。
- 阈值判断逻辑缺陷:依赖固定屏幕比例判断展开状态,在不同设备或Sheet过渡过程中易出现状态反复切换,加剧动画冲突。
修复方案
1. 用Sheet Detent状态替代尺寸监听
直接通过@Environment(\.presentationDetent)获取当前Sheet的展开状态,避免频繁的尺寸变化回调。
2. 统一Matched Geometry命名空间
在父视图和Sheet视图间共享命名空间,确保动画上下文一致。
修改后的完整代码示例
struct ProfileListView: View { @State var navigateToActivityDetail: Bool = false @State private var isProfileExpanded: Bool = false @Namespace private var animationNamespace // 共享命名空间 var body: some View { VStack { // 示例触发按钮 Button("打开用户详情") { navigateToActivityDetail = true } } .sheet(isPresented: $navigateToActivityDetail) { ProfileDetailDetailView( isProfileExpanded: $isProfileExpanded, animationNamespace: animationNamespace ) .presentationDetents([.medium, .large, .fraction(0.75)]) .background(Color(ColorConstants.GeneralElevation1).edgesIgnoringSafeArea(.all)) } } } struct ProfileDetailDetailView: View { @Binding var isProfileExpanded: Bool let animationNamespace: Namespace.ID @Environment(\.presentationDetent) private var currentDetent var body: some View { VStack(spacing: 32) { DrawerStateUser(isProfileExpanded: $isProfileExpanded) .matchedGeometryEffect(id: "userProfileContainer", in: animationNamespace) } .padding(.top, 40) .onChange(of: currentDetent) { newDetent in withAnimation(.easeInOut(duration: 0.3)) { // 根据实际Detent映射展开状态,这里假设medium为折叠,large/0.75为展开 isProfileExpanded = newDetent == .medium } } } } // 确保DrawerStateUser内部视图也应用matchedGeometryEffect struct DrawerStateUser: View { @Binding var isProfileExpanded: Bool let animationNamespace: Namespace.ID var body: some View { HStack(alignment: .center, spacing: isProfileExpanded ? 16 : 8) { // 头像视图 Image(systemName: "person.circle") .frame(width: 60, height: 60) .matchedGeometryEffect(id: "userAvatar", in: animationNamespace) // 详情文本 VStack(alignment: isProfileExpanded ? .leading : .center) { Text("用户名") .matchedGeometryEffect(id: "userName", in: animationNamespace) Text("用户描述") .matchedGeometryEffect(id: "userDesc", in: animationNamespace) } } } }
关键注意事项
- 所有参与Matched Geometry动画的视图必须使用相同的id和命名空间。
- 避免用
if/else直接切换视图存在性,改用opacity、offset或scaleEffect控制显示,保持视图树稳定。 - 动画时长与Sheet过渡动画保持一致,避免时序冲突。
内容的提问来源于stack exchange,提问作者Athul Sethu
相关产品推荐
相关产品推荐

