问题
I am trying to choreograph animations in SwiftUI. I want two colored bars to move in to the view with a delay between them and then move out of the view with the delays switched so that the removal is the reverse of the insertion.
I think I understand why the below code doesn't work, but I can't figure out how to make it do what I need:
import SwiftUI
struct TestAnimControl: View {
@State var show: Bool = false
@State var reverseDelay: Bool = false
var body: some View {
VStack {
Button(action:{
self.show.toggle()
}) {
Text("Animate")
.font(.largeTitle)
}
if show {
Rectangle()
.foregroundColor(.blue)
.frame(height: 100)
.transition(.move(edge: .trailing))
.animation(Animation.spring().delay(show ? 0.3 : 0.5))
Rectangle()
.foregroundColor(.red)
.frame(height: 100)
.transition(.move(edge: .trailing))
.animation(Animation.spring().delay(show ? 0.5 : 0.3))
}
}
}
}
When you run this and hit the button, the blue bar moves in and then the red bar moves in. Hit the button again and the blue bar moves out and then the red bar moves out. What I want is when you hit the button for removal, the red bar moves out and then the blue bar moves out, reverse of the way the bars came in. In this code I believe the ternary doesn't work because the animation is set when the Rectangle is created and the delay can't change after that. I may be wrong, but either way is there a way to do what I am trying to do?
回答1:
Here is a solution. Tested with Xcode 11.4 / iOS 13.4
struct TestAnimControl: View {
@State var show: Bool = false
@State var reverseDelay: Bool = false
@State var blueDelay = 0.3
@State var redDelay = 0.5
var body: some View {
VStack {
Button(action:{
self.show.toggle()
}) {
Text("Animate")
.font(.largeTitle)
}
if show {
Rectangle()
.foregroundColor(.blue)
.frame(height: 100)
.transition(.move(edge: .trailing))
.animation(Animation.spring().delay(blueDelay))//(show ? 0.3 : 0.5))
.onAppear { self.blueDelay = 0.5 }
.onDisappear { self.blueDelay = 0.3 }
Rectangle()
.foregroundColor(.red)
.frame(height: 100)
.transition(.move(edge: .trailing))
.animation(Animation.spring().delay(redDelay))//(show ? 0.5 : 0.3))
.onAppear { self.redDelay = 0.3 }
.onDisappear { self.redDelay = 0.5 }
}
}
}
}
回答2:
You cannot change delay value according to state value because Animation struct is marked as @frozen that has no effect on property observers - state properties (check it out https://docs.swift.org/swift-book/ReferenceManual/Attributes.html). The proper way to do is using DispatchQueue.main.asyncAfter(deadline:_:) to specify each delay. Here is my sample code...
struct ContentView: View {
@State var blue = false
@State var red = false
var body: some View {
VStack {
Button(action:{
let redDelay = self.red ? 0.3 : 0.5
DispatchQueue.main.asyncAfter(deadline: .now() + redDelay) {
self.red.toggle()
}
let blueDelay = self.blue ? 0.5 : 0.3
DispatchQueue.main.asyncAfter(deadline: .now() + blueDelay) {
self.blue.toggle()
}
}) {
Text("Animate")
.font(.largeTitle)
}
if blue {
Rectangle()
.foregroundColor(.blue)
.frame(height: 100)
.transition(AnyTransition.move(edge: .trailing))
.animation(Animation.spring())
}
if red {
Rectangle()
.foregroundColor(.red)
.frame(height: 100)
.transition(AnyTransition.move(edge: .trailing))
.animation(Animation.spring())
}
}
.animation(.spring())
}
}
Thanks. X_X
来源:https://stackoverflow.com/questions/62141416/swiftui-inverse-animation-delay-on-removal