Delay a Transition in Swiftui

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 ifs, 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:

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 ids 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.

Sample Image

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.

demo

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



Leave a reply



Submit