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

带加载状态的图片为何无法正常触发matchedGeometryEffect动画?

问题分析与解决方案

问题根源

你的代码里,matchedGeometryEffect仅在loaded为true时才附加到Image上。切换bigger状态时,两个分支的TheImage可能存在其中一个未完成加载(Image未渲染)的情况,导致matchedGeometryEffect找不到对应的匹配视图,动画直接失效。

如果把matchedGeometryEffect移到Group层级,虽然动画能运行,但因为Group在加载前后(ProgressView和Image)的尺寸差异极大,会出现奇怪的缩放表现。

修复方案

核心思路是让matchedGeometryEffect作用在一个始终存在的容器视图上,通过透明度控制内容显示,避免if-else分支导致视图销毁重建,确保几何匹配的载体始终存在。

修改后的代码如下:

@MainActor
@Observable
final class ViewModel {
    var bigger: Bool? = nil
    var expansionAnimationNamespace: Namespace.ID? = nil
}

struct TheImage: View {
    let id = "HI"
    let viewModel: ViewModel
    
    @State var loaded: Bool = false
    var body: some View {
        ZStack {
            ProgressView()
                .opacity(loaded ? 0 : 1)
            
            Image(systemName: "person")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .opacity(loaded ? 1 : 0)
        }
        .matchedGeometryEffect(id: id, in: viewModel.expansionAnimationNamespace ?? Namespace().wrappedValue)
        .task {
            try? await Task.sleep(nanoseconds: 200)
            self.loaded = true
        }
    }
}

struct ContentView: View {
    @State var viewModel = ViewModel()
    @Namespace var namespace
    
    var body: some View {
        NavigationStack {
            Group {
                if viewModel.bigger == true {
                    TheImage(viewModel: viewModel)
                        .toolbar {
                            Button {
                                viewModel.bigger = nil
                            } label: {
                                Text("Smaller")
                            }
                        }
                } else {
                    TheImage(viewModel: viewModel)
                        .frame(width: 100, height: 100)
                        .toolbar {
                            Button {
                                viewModel.bigger = true
                            } label: {
                                Text("Larger")
                            }
                        }
                }
            }
        }
        .task {
            viewModel.expansionAnimationNamespace = namespace
        }
        .animation(.easeInOut, value: viewModel.bigger)
    }
}

说明

  • 用ZStack替代原来的Group+if-else,让ProgressView和Image始终存在于视图树中,只是通过opacity控制显示/隐藏,保证matchedGeometryEffect的载体(ZStack)在切换bigger状态时始终能找到对应的匹配视图。
  • matchedGeometryEffect移到ZStack层级,确保几何匹配的是整个容器的尺寸,既解决了加载状态导致的动画失效问题,也避免了奇怪的缩放表现。

内容的提问来源于stack exchange,提问作者CalebK

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 20:03:24