Delay a transition in SwiftUI
If you add an explicit id
it works as you would like. Note, I made only one animation delay to make it a little more obvious that this is working.
struct ContentView: View {
@State var showOne = true
var body:some View {
VStack {
if showOne {
HStack {
Spacer()
Text("One")
Spacer()
}
.background(Color.red)
.id("one")
.animation(Animation.default)
.transition(.slide)
} else {
HStack {
Spacer()
Text("Two")
Spacer()
}
.background(Color.blue)
.id("two")
.animation(Animation.default.delay(2))
.transition(.slide)
}
Button("Toggle") {
withAnimation {
self.showOne.toggle()
}
}
}
}
}
I've found an explicit id
to be helpful most of the time I want to use a transition. In your example, not using the id
can cause the text to change before the background. This would seem to be a bug, and I recommend filing feedback on it.
Delay SwiftUI combined transitions
Try this
extension AnyTransition {
static var delayAndFade: AnyTransition {
return AnyTransition.identity
.combined(with: .opacity)
.animation(.default.delay(3))
}
}
If you want to move a view, you should animate its offset
using the withAnimation
function.
Text("Move and fade.")
.offset(y: offset)
.transition(.delayAndFade)
struct ContentView: View {
@State private var showDetails = false
@State var offset:CGFloat = 0
var body: some View {
VStack {
Button("Press to show details") {
showDetails.toggle()
withAnimation(.default.delay(3)) {
self.offset = -20
}
}
if showDetails {
Text("Move and fade.")
.offset(y: offset)
.transition(.delayAndFade)
}
}
}
}
Update
extension AnyTransition {
static var moveAndFade: AnyTransition {
return AnyTransition.move(edge: .top)
.combined(with: .opacity)
}
}
Try this
HStack {
Text("Move and fade.")
}
.animation(Animation.default.delay(2))
.transition(.moveAndFade)
It works with all kind of views except Text.
struct ContentView: View {
@State private var showDetails = false
@State var offset:CGFloat = 0
var body: some View {
VStack {
Button("Press to show details") {
showDetails.toggle()
}
if showDetails {
// Works!
HStack {
Text("Move and fade.")
}
.animation(Animation.default.delay(2))
.transition(.moveAndFade)
Button("Move and fade.") {}
.animation(Animation.default.delay(2))
.transition(.moveAndFade)
// Does not work
Text("Move and fade.")
.animation(Animation.default.delay(2))
.transition(.moveAndFade)
}
}
}
}
Delay transition of a button
You are quite close. I just split up the single if
-else
into 2 if
s, wrapped in a ZStack
.
The button also makes secondaryDelay
equal true
for half a second, and then it switches back to false
.
When the view leaves the hierarchy, that's when the transition starts. I worked out the way to do this by considering all the scenarios for the selectImageButton
:
- Showing - show: delayedMove = true, secondaryDelay = false
- Starting to hide - hide: delayedMove = false, secondaryDelay = true
- Hiding - hide: delayedMove = false, secondaryDelay = false
- Starting to show - hide: delayedMove = true, secondaryDelay = true
From above, we only need to show when delayedMove && !secondaryDelay
equals true
. Similar method for other deleteImage
.
Code:
struct ContentView: View {
@State var delayedMove = false
@State var secondaryDelay = false
var body: some View {
moveMe
}
@ViewBuilder
var moveMe: some View {
HStack {
Spacer()
Button(action: {
withAnimation {
delayedMove.toggle()
secondaryDelay = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
secondaryDelay = false
}
}
}) {
ZStack {
if delayedMove && !secondaryDelay {
selectImageButton
.animation(.linear)
.transition(.move(edge: .trailing))
}
if !delayedMove && !secondaryDelay {
deleteImage
.animation(.linear)
.transition(.move(edge: .trailing))
}
}
}
.padding()
}
}
}
Result:
How to delay animation in SwiftUI?
From the partial code you provided it seems you are manipulating the same variables in both closures. Change that to individual ones for each field.
Reason:
when you do:
@State private var changeVar = 0.0
withAnimation(...){
changeVar = 1.0
}
SwiftUI will change that value incrementally from the value it is at the moment the animation starts to the value provided in the closure. As soon as the value changes the view gets redrawn with the current value of changeVar
. As you use the same var for both Views the changes are applied to both at the same time. Hence the animation starts for both at the same time.
SwiftUI animations with delay
In order to animate the characters onto the screen, they need to be offscreen first. That means you need to build up the array of letters your ForEach
is showing.
I made the letters Identifiable
giving them unique id
s so that it wouldn't confuse the first T
in START
with the second T
.
I'm using an offset
for animation
instead of slide
. The arriving
property of a letter is used to decide the direction of the offset
.
struct Letter: Identifiable {
let letter: String
var arriving: Bool
let id = UUID()
}
struct ContentView: View {
@State private var start = [" ","S","T","A","R","T"].map { Letter(letter: $0, arriving: true) }
@State private var letters = [Letter]()
@State private var timer = Timer.publish(every: 2, tolerance: 0.5, on: .main, in: .common).autoconnect()
var body: some View {
ZStack {
HStack {
ForEach(self.letters) { letter in
Text(letter.letter)
.font(.custom("Menlo", size: 18))
.fontWeight(.black)
.frame(width: 38, height: 38, alignment: .center)
.background(Color.red)
.clipShape(Circle())
.foregroundColor(.white)
.shadow(radius: 10, x: 10, y: 10)
.transition(AnyTransition.offset(x: letter.arriving ? -250 : 250))
.animation(Animation.linear(duration: 1).repeatCount(1))
}
}
}
.onReceive(timer) {_ in
print("TIMER")
var letter = start.removeLast()
letter.arriving = true
letters.indices.forEach { idx in letters[idx].arriving = false }
letters = [letter] + letters
if letters.count > 5 {
let last = letters.removeLast()
start = [last] + start
}
}
}
}
SwiftUI inverse animation delay on removal
Update: Xcode 13.4 / iOS 15.5
A proposed solution now is based on explicit animation with modification in every transaction so each transition have own parametrised variant of animation.
Main part:
Button(action:{
withAnimation {
self.show.toggle()
}
}) {
Text("Animate")
.font(.largeTitle)
}
if show {
Rectangle()
.foregroundColor(.blue)
.frame(height: 100)
.transition(.move(edge: .trailing))
.transaction {
$0.animation = Animation.spring().delay(delay1)
}
.onAppear { self.delay1 = 0.5 }
.onDisappear { self.delay1 = 0.3 }
Rectangle()
.foregroundColor(.yellow)
.frame(height: 100)
.transition(.move(edge: .trailing))
.transaction {
$0.animation = Animation.spring().delay(delay2)
}
.onAppear { self.delay2 = 0.3 }
.onDisappear { self.delay2 = 0.5 }
}
Test code on GitHub
Original:
!!! It does not work anymore with modern OS
Here is a solution - based on applied implicit animations to every transition. 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 }
}
}
}
}
How to create animation with delay when dismiss view swiftui
This would be one way to do it. Without the NavigationLink
you have full control over all animations and transitions.
struct DetailView: View {
@Binding var showDetail:Bool
var body: some View {
Button(
"Here is Detail View. Tap to go back.",
action: {
withAnimation(Animation.linear.delay(2)){
self.showDetail = false
}
}
).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).background(Color.yellow)
}
}
struct RootView: View {
@State var showDetail = false
var body: some View {
VStack {
if showDetail{
DetailView(showDetail:self.$showDetail).transition(.move(edge: .trailing))
}else{
Button("I am Root. Tap for Detail View."){
withAnimation(.linear){
self.showDetail = true
}
}
}
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).background(Color.red)
}
}
Related Topics
Navigationview Bar Material Invisible on iOS 15
Reading Data from Excel Document in a Swift App
Difference Between Optional Values in Swift
Check If a Func Exists in Swift
Swift Vscode Class in Other File Not Recognized
Function Builder Not Working When Only One Value
Maccatalyst App: How to Close a Window Without Terminating The App
Uibutton Font Size Isn't Changing
Send Mail with File Attachment
How to Customize the Title/Subtitle Font in Callout from Mkannotationview or Just Hide Them
Video Upload to Amazon S3 in Swift
Swift: Reduce Function with a Closure
Use of Undeclared Type Autoreleasingunsafepointer Xcode 6 Beta 6
How Many Way Are There to Do Crud Operation in Sqlite Swift
How to Access Content View's Elements Later in Swiftui