iOS 15 AsyncImage结合Matched Geometry Effect过渡异常问题求解
问题根因说明
- 白色闪烁是因为你将两个AsyncImage放在if/else分支中,切换时两个视图会经历销毁、重建的间隙,ZStack默认的白色底层背景会在间隙露出,同时AsyncImage重建时加载间隙的占位灰也会加剧闪烁效果。
- AsyncImage偶发进入placeholder是因为每次状态切换都会新建AsyncImage实例,哪怕做了缓存,视图重建时AsyncImage内部状态也会重置,触发重新加载逻辑,加载间隙就会显示占位,导致动画卡顿。
可行解决思路
核心逻辑:避免重复创建AsyncImage实例
只加载一次图片,将加载完成的图片实例存在状态变量中,切换状态时直接复用已加载的图片,完全避免重复触发加载逻辑。同时统一底层背景色,消除视图切换间隙的漏白问题。
优化后代码
struct ContentView: View { @State var showSplash: Bool = true @Namespace var animationNamespace // 存储加载完成的图片实例,避免重复加载 @State private var loadedImage: Image? var body: some View { ZStack { // 统一底层背景,避免切换间隙漏白 Color.black.edgesIgnoringSafeArea(.all) // 仅执行一次图片加载逻辑 AsyncImage(url: URL(string: "https://picsum.photos/seed/864a5875-6d8b-43d6-8d65-04c5cfb13f3b/1920/1440")) { phase in switch phase { case .success(let image): Color.clear.onAppear { loadedImage = image } case .empty, .failure(_): Color.gray @unknown default: Color.gray } } // 复用已加载的图片做状态切换 if let image = loadedImage { ZStack { if showSplash { GeometryReader { geometry in image .resizable() .scaledToFill() .matchedGeometryEffect(id: "SplashImage", in: animationNamespace) .frame(width: geometry.size.width) .edgesIgnoringSafeArea(.all) .clipped() } .onTapGesture { toggleSplashScreen(false) } } else { ScrollView { GeometryReader { geometry in image .resizable() .scaledToFill() .matchedGeometryEffect(id: "SplashImage", in: animationNamespace) .frame(width: geometry.size.width, height: 400) .clipped() } .edgesIgnoringSafeArea(.all) .onTapGesture { toggleSplashScreen(true) } } } } } } } } private extension ContentView { func toggleSplashScreen(_ toggle: Bool) { withAnimation(.spring(response: 0.85, dampingFraction: 0.95)) { showSplash = toggle } } }
额外优化建议
- 全局配置
URLCache,确保首次网络加载后的图片都走本地缓存,避免冷启动首次加载过慢 - 可以增加加载完成判断,图片加载成功后再显示交互入口,避免首次加载未完成时点击出现异常
内容的提问来源于stack exchange,提问作者Antonio Chan
相关产品推荐
相关产品推荐

