ios - 与 AsyncImage iOS 15 匹配的几何效果

标签 ios swiftui ios15 swiftui-geometryeffect swiftui-asyncimage

考虑以下示例:

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

这会产生:

Demo

我注意到这里有两件事我想修复

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

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

最佳答案

我认为您可以采取一些措施来改进这一点。

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

GeometryReader 
  AsyncImage

ScrollView
  GeometryReader
    AsyncImage

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

这就引出了您要调查的第二个领域。 AsyncImage 的美妙之处在于它为您从网络加载内容提供了便利。不幸的是,它并没有使通信速度更快。您的目标应该是让 AsyncImage 访问网络的次数尽可能少。

现在,您的调整大小策略侧重于调整图像大小。这意味着对于每次转换,您都在“访问网络”(请阅读将代码放在缓慢、尘土飞扬的土路上)。您应该只加载一次图像(缓慢的部分)并调整显示它的 View 的大小,而不是调整图像的大小。总体思路是让 AsyncImage 加载图像,然后通过对 View 框架进行动画处理来控制图像的动画效果。

这是我得到的帮助较少的地方。我对 AsyncImage 的了解不够,不知道它是否能够实现该策略。似乎应该是……但我不知道是不是。您可能必须下载图像并将其存储为与呈现图像的 View 分开的状态。

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

关于ios - 与 AsyncImage iOS 15 匹配的几何效果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69673264/

相关文章:

javascript - 如何在 iPadOS15 Safari 中禁用放大镜

ios - 命令 CompileSwiftSources 失败,退出代码为非零 XCode 13

ios - 检查来自 iOS 设备的 TCP 流量

ios - SwiftUI:创建自定义警报

ios - Xcode 7,用户界面测试 : working with UITableView

arrays - SWIFTUI:使用 Array 属性创建类 - 符合 'ObservableObject' 。类(class)!不是结构体

ios - 在 ScrollView SwiftUI 中添加时列表隐藏

swiftui - 如何在 iOS 15 中使用 SwiftUI 在特定 View 中隐藏 TabBar

android - 移动应用——在后端存储和版本化相对静态的json文件

ios - Apple允许自定义localizedFallbackTitle进行本地认证吗?