带加载状态的图片为何无法正常触发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
相关产品推荐
相关产品推荐

