如何让替换视图淡入旧视图之上而不是与其交叉淡入淡出?

Sea*_*ber 5 ios swiftui

默认情况下,SwiftUI 似乎使用过渡.opacity,因此当视图在动画期间出现/消失时,它会淡入/淡出。这会创建一种交叉淡入淡出效果,通常效果很好,但当一个重叠视图替换另一个视图时,有时可能会出现不良效果。

\n

我遇到的情况是,我\xe2\x80\x99d 宁愿让新视图淡入即将被替换的视图之上。旧视图根本不应该改变不透明度,而应该在转换完成后消失。

\n

我不希望在转换过程中的任何时刻我看到旧视图的透明度只有 100%。例如,默认的处理方式会导致过渡中间的某个点,在该点我们会看到后视图和前视图的 50% 不透明度版本相互叠加。

\n

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

\n

这是一些显示我的意思的代码。顶部视图使用默认的过渡和交叉淡入淡出显示,但代码按照我期望的方式显示 - 当新视图存在时,我们只使用它。底部按照我想要的方式显示 - 但这样做的代价是始终将背景视图保留在那里,以便只有新视图在第一次出现时才会消失:

\n
import SwiftUI\nimport PlaygroundSupport\n\nstruct ContentView: View {\n    let transaction = Transaction(animation: .linear(duration: 10))\n    let imageURL = URL(string: "https://www.nasa.gov/sites/default/files/thumbnails/image/main_image_star-forming_region_carina_nircam_final-5mb.jpg")!\n    \n    var body: some View {\n        VStack(spacing: 10) {\n            AsyncImage(url: imageURL, transaction: transaction) { phase in\n                if let img = phase.image {\n                    img.resizable()\n                } else {\n                    Color.red\n                }\n            }\n            .aspectRatio(CGSize(width: 3600, height: 2085), contentMode: .fit)\n\n            AsyncImage(url: imageURL, transaction: transaction) { phase in\n                ZStack {\n                    Color.red\n                    \n                    if let img = phase.image {\n                        img.resizable()\n                    }\n                }\n            }\n            .aspectRatio(CGSize(width: 3600, height: 2085), contentMode: .fit)\n        }\n        .frame(width: 500)\n        .padding(10)\n        .background(Color.yellow)\n    }\n}\n\nPlaygroundPage.current.setLiveView(ContentView())\n
Run Code Online (Sandbox Code Playgroud)\n

由于我显然无法嵌入视频,这里有一个正在运行的操场之一的链接,因为它有助于看到它以理解我在这里的意思,我认为:https ://www.dropbox.com/s/scwxfoa9pojo0yq /SwiftUICrossfade.mov?dl=0

\n

aus*_*nts 5

一种方法可能是将自定义过渡应用到背景视图。我只是简单地测试了这一点,但添加了这一点后,顶部视图和底部视图在转换过程中看起来是匹配的。

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())

Run Code Online (Sandbox Code Playgroud)

  • 这是一个很好的观点,在我最初的示例中,“不透明度”修饰符是多余的,因为目标是不透明度永远不会改变。我责怪这样一个事实:当我写道我编辑了答案以删除它并添加一些评论以便将来的读者清楚时,我也试图跟上一个小孩。 (2认同)