You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
}

效果示意图:
折叠状态
展开状态

崩溃原因分析

  1. 尺寸监听频繁触发冲突:Sheet尺寸变化是连续动画过程,proxy.size.height会反复触发onChange回调,多次修改isProfileExpanded状态,导致Matched Geometry动画时序错乱。
  2. Matched Geometry上下文缺失:未正确配置共享命名空间(Namespace),或状态切换时视图被销毁/重建,引发布局树不稳定。
  3. 阈值判断逻辑缺陷:依赖固定屏幕比例判断展开状态,在不同设备或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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 04:46:35