2

考虑以下示例:

    struct ContentView: View {

    @State var showSplash: Bool = true
    @Namespace var animationNamespace

    var body: some View {
        ZStack {
            if showSplash {
                GeometryReader { geometry in
                    AsyncImage(url: URL(string: "https://picsum.photos/seed/864a5875-6d8b-43d6-8d65-04c5cfb13f3b/1920/1440")) { image in
                        image.resizable()
                        .scaledToFill()
                        .matchedGeometryEffect(id: "SplashImage", in: animationNamespace)
                        .transition(.move(edge: .bottom))
                        .frame(width: geometry.size.width)
                        .transition(.move(edge: .bottom))
                        .edgesIgnoringSafeArea(.all)
                        .clipped()
                    } placeholder: {
                        Color.gray
                    }
                }
                .onTapGesture {
                    toggleSplashScreen(false)
                }
            } else {
                ScrollView {
                    GeometryReader { geometry in
                        AsyncImage(url: URL(string: "https://picsum.photos/seed/864a5875-6d8b-43d6-8d65-04c5cfb13f3b/1920/1440")) { image in
                            image
                            image
                                .resizable()
                                .scaledToFill()
                                .matchedGeometryEffect(id: "SplashImage", in: animationNamespace)
                                .transition(.move(edge: .bottom))
                        } placeholder: {
                            Color.gray
                        }
                        .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
        }
    }
}

这会产生:

演示

我注意到这里有两件事我想解决

  1. 在两种状态之间转换时闪烁的白色效果。
  2. 我注意到,因为我们使用的是AsyncImage,当showSplash更改 AsyncImages 时,有时只会碰到placeholder块。结果,过渡变得非常不稳定。我使用资产文件中的静态图像对此进行了测试,然后过渡变得平滑。我还尝试在 AsyncImage 上创建缓存机制,但有时仍会遇到placeholder阻塞问题。

很想听听任何想法:) 谢谢!

4

1 回答 1

3

我认为你可以做几件事来改善这一点。

首先,您正在与 SwiftUI 维护视图身份的方式作斗争。SwiftUI 确定何时可以重用现有结构而不是重新创建结构的方法之一是通过它在视图层次结构中的位置。因此,当您切换结构时,您将从:

GeometryReader 
  AsyncImage

ScrollView
  GeometryReader
    AsyncImage

结果,系统认为这是两个AsyncImage视图,因此它每次都重建视图(并重新加载图像)。我认为这就是您的白色闪光的来源,因为您在动画中间看到了灰色占位符。如果您可以保留滚动视图,可能在不需要时禁用滚动(如果可能的话),那么操作系统可以维护AsyncImage. (见https://developer.apple.com/videos/play/wwdc2021/10022/

这导致了您的第二个调查领域。 AsyncImage它为您从网络加载内容提供了便利,非常棒。不幸的是,它并没有使这种通信更快。您的目标应该是AsyncImage尽可能少地访问网络。

现在,您的调整大小策略侧重于调整图像大小。这意味着对于每次转换,您都在“访问网络”(阅读将您的代码放在缓慢、尘土飞扬、泥泞的道路上)。而不是调整图像的大小,您应该只加载一次图像(慢速部分)并调整显示它的视图的大小。一般的想法是让AsyncImage加载图像,然后通过动画视图的框架来控制图像的动画方式。

这是我得到较少帮助的地方。我不太了解AsyncImage它是否能够实施该战略。似乎应该是……但我不知道是不是。您可能不得不求助于将图像下载并存储为与呈现它的视图分开的状态。

所以我的建议是限制AsyncImage重新加载网络数据的次数。这涉及帮助 SwiftUI 维护 的身份,AsyncImage因此它不必在每次创建视图时重新加载。并且,尝试在视图而不是图像上实现动画和缩放,因为重新缩放图像还需要重新加载网络。

于 2021-10-22T13:25:24.697 回答