默认情况下,SwiftUI过渡似乎使用.opacity,因此当动画期间显示/消失视图时,它将淡入/淡出.这会产生通常非常好的交叉淡入淡出效果,但当一个重叠视图替换另一个重叠视图时,有时可能不太理想.

我有一种情况,我宁愿让新的视图淡入即将被替换的视图的顶部.旧的视图应该根本不会改变不透明度,而只是在过渡完成后消失.

我不希望在过渡期间出现任何时候,除了100%不透明之外,我看到的都是旧的观点.例如,默认的处理方式会导致在过渡中间的一个点上,我们可以看到50%的不透明度版本的后视图和前视图相互叠加.

需要明确的是,我已经有了一个解决办法:如果我使用ZStack始终保持背景视图在那里,我可以获得我想要的效果-这样,只有新的视图淡入(因为它是唯一更改的视图).然而,我的解决方案感觉是错误的、浪费的和不优雅的.(背景中的视图将不断地被系统合成,尽管在实际图像加载后它是完全不可见的和不需要的.我只希望背景视图在转换完成之前一直存在,但我不知道如何让它做到这一点.)

下面是一些代码,说明了我的意思.顶视图使用默认的过渡和交叉淡入淡出显示,但使用代码的方式与我预期的一样--当新视图存在时,我们使用它,并且只使用它.底部以我希望的方式显示-但这样做的代价是始终保持背景视图在那里,以便只有新视图在第一次出现时会褪色:

import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    let transaction = Transaction(animation: .linear(duration: 10))
    let imageURL = URL(string: "https://www.nasa.gov/sites/default/files/thumbnails/image/main_image_star-forming_region_carina_nircam_final-5mb.jpg")!
    
    var body: some View {
        VStack(spacing: 10) {
            AsyncImage(url: imageURL, transaction: transaction) { phase in
                if let img = phase.image {
                    img.resizable()
                } else {
                    Color.red
                }
            }
            .aspectRatio(CGSize(width: 3600, height: 2085), contentMode: .fit)

            AsyncImage(url: imageURL, transaction: transaction) { phase in
                ZStack {
                    Color.red
                    
                    if let img = phase.image {
                        img.resizable()
                    }
                }
            }
            .aspectRatio(CGSize(width: 3600, height: 2085), contentMode: .fit)
        }
        .frame(width: 500)
        .padding(10)
        .background(Color.yellow)
    }
}

PlaygroundPage.current.setLiveView(ContentView())

由于我显然不能嵌入视频,这里有一个指向一个正在运行的playground 的链接,因为它有助于看到它来理解我这里的意思,我想:https://www.dropbox.com/s/scwxfoa9pojo0yq/SwiftUICrossfade.mov?dl=0

推荐答案

一种方法可能是将自定义过渡应用于背景视图.我只是简单地测试了一下,但是添加了这个之后,俯视图和俯视图在转换过程中看起来是匹配的.

import SwiftUI
import PlaygroundSupport

struct AllOrNothingTransition: Animatable, ViewModifier {
    
    var animatableData: CGFloat = 0
    
    func body(content: Content) -> some View {
        // The goal is for `content` to remain in place, unchanged,
        // until the transition completes and it is removed, so we
        // simply pass it back unchanged regardless of the value
        // of `animatableData`.
        content
    }
}

extension AnyTransition {
    static var allOrNothing: AnyTransition {
        // For this use case, the specific values passed in as `animatableData`
        // are not important, as long as they differ from each other.
        // SwiftUI needs to have differing values to interpolate between,
        // otherwise it will assume there is nothing to animate and
        // remove the transitioning view immediately at the start of
        // of the transition (this is based on observation, not a knowledge
        // of the inner workings of SwiftUI).
        AnyTransition.modifier(
            active: AllOrNothingTransition(animatableData: 0),
            identity: AllOrNothingTransition(animatableData: 1)
        )
    }
}

struct ContentView: View {
    let transaction = Transaction(animation: .linear(duration: 10))
    let imageURL = URL(string: "https://www.nasa.gov/sites/default/files/thumbnails/image/main_image_star-forming_region_carina_nircam_final-5mb.jpg")!
    
    var body: some View {
        VStack(spacing: 10) {
            AsyncImage(url: imageURL, transaction: transaction) { phase in
                
                if let img = phase.image {
                    img.resizable()
                } else {
                    Color.red
                        .transition(.allOrNothing)
                }
            }
            .aspectRatio(CGSize(width: 3600, height: 2085), contentMode: .fit)

            AsyncImage(url: imageURL, transaction: transaction) { phase in
                ZStack {
                    Color.red
                    
                    if let img = phase.image {
                        img.resizable()
                    }
                }
            }
            .aspectRatio(CGSize(width: 3600, height: 2085), contentMode: .fit)
        }
        .frame(width: 500)
        .padding(10)
        .background(Color.yellow)
    }
}

PlaygroundPage.current.setLiveView(ContentView())

Ios相关问答推荐

[NSJSONSerializationdataWithJSONObserver:选项:错误:]:SON写入中无效的顶级类型

在选项卡内列出导航内视图断开导航标题的动画

iOS 17-在Xcode 15中添加导航控制器时,导航栏不会立即显示

无法关闭包含字体 Select 器的工作表

swiftui 图像放大时无法正确拖动

RFID scanner 的蓝牙服务 UUID?

从 PHAssets 生成图像时,iOS 应用程序因内存问题而崩溃

如何在 SwiftUI 中将选项卡放在滚动视图中?

iOS SwiftUI - 从以前的 struct 调用函数

我们可以在 Swift 中将 struct 中的 Codable 键设置为不区分大小写吗

如何使用 Swift 文档注释

在 Swift 中按下返回键时在文本字段之间切换

在设备上卸载 ios 应用程序后如何在 ios 中保留 identifierForVendor?

ios 崩溃 EXC_BAD_ACCESS KERN_INVALID_ADDRESS

在 UITextView 中垂直居中文本

Xcode 4.1 致命错误:自构建预编译头文件后修改了 stdlib

从 WKWebView 获取所有 cookie

以编程方式 Select UITextField 中的所有文本

如何使用完成按钮制作 UIPickerView?

openURL:在 iOS 10 中已弃用