默认情况下,SwiftUI 转换似乎使用 .opacity
因此当 View 在动画期间出现/消失时,它将淡入/淡出。这会创建一个交叉淡入淡出效果,这种淡入淡出效果通常非常好,但有时在一个重叠 View 替换另一个 View 时可能不受欢迎。
我有一种情况,我宁愿让新 View 淡入到即将被替换的 View 之上。旧 View 根本不应该改变不透明度,而只是在转换完成后消失。
我不希望在过渡期间看到旧 View 不是 100% 不透明度的任何点。例如,默认的处理方式会导致过渡中间的一个点,在该点我们会看到后 View 和前 View 的 50% 不透明度版本相互叠加。
明确地说,我已经有了一个解决方法:如果我使用 ZStack 始终保持背景 View ,我可以获得我想要的效果 - 这样只有新 View 淡入(因为它是唯一的 View 变化)。不过,我的解决方案感觉是错误的、浪费的和不雅的。 (尽管在实际图像加载后它完全不可见且不需要,但系统会不断合成背景 View 。我只希望该背景 View 存在直到转换完成,但我无法弄清楚如何让它做到这一点。)
这里有一些代码可以说明我的意思。顶部 View 使用默认过渡和淡入淡出显示,但代码符合我的预期 - 当新 View 存在时,我们使用它并且只使用它。底部以我想要的方式出现 - 但这样做的代价是始终将背景 View 保持在那里,以便只有新 View 在首次出现时才会消失:
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
最佳答案
一种方法可能是将自定义过渡应用于背景 View 。我只是简单地测试了这个,但添加了这个后,顶部和底部 View 在转换过程中看起来是匹配的。
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 - 如何让替换 View 淡入旧 View 而不是与它交叉淡入淡出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73409016/