Transition Animation Not Working in Swiftui

Transition animation not working in SwiftUI

The problem is that when views come and go in a ZStack, their "zIndex" doesn't stay the same. What is happening is that the when "showMessage" goes from true to false, the VStack with the "Hello World" text is put at the bottom of the stack and the yellow color is immediately drawn over top of it. It is actually fading out but it's doing so behind the yellow color so you can't see it.

To fix it you need to explicitly specify the "zIndex" for each view in the stack so they always stay the same - like so:

struct ContentView: View {
@State private var showMessage = false

var body: some View {
ZStack {
Color.yellow.zIndex(0)

VStack {
Spacer()
Button(action: {
withAnimation(.easeOut(duration: 3)) {
self.showMessage.toggle()
}
}) {
Text("SHOW MESSAGE")
}
}.zIndex(1)

if showMessage {
Text("HELLO WORLD!")
.transition(.opacity)
.zIndex(2)
}
}
}

}

SwiftUI Transition not happening

You need an animation (to animate transition) and a container (which performs actual transition, because default implicit Group does not do that).

Here is fixed part of code (tested with Xcode 13.2 / iOS 15.2)

*Note:Preview > Debug > Slow Animation for better visibility

demo

var body: some View {
VStack { // << this !!
if self.viewModel.model.show {
Text("Showing")
.padding()
} else {
Text("Not Showing")
.padding()
.transition(.asymmetric(insertion: .scale, removal: .opacity))
}
}
.animation(.default, value: self.viewModel.model.show) // << here !!

Button {
self.viewModel.show()
} label: {
Text("Tap to change")
}
}

SwiftUI Can't Animate Image Transitions

The current approach with SwiftUI is to use .transition(), because .animation() is being deprecated.

What is important to understand is that .transition() is triggered when a view appears or disappears. Your view will not be completely re-drawn just because you change a @State variable: in your code, the Image changes but it always stays in the view.

One solution is to trigger the image to completely disappear and make a new one re-appear. The code below does that, depending on the state of showAnimation. See that I only used .transition(), but for a nice effect:

  • it is asymmetric
  • the withAnimation() closure wraps also the changing of the currentIndex
struct Example: View {
let photos = ["gear", "person", "person.2", "car", "leaf"]

@State private var currentIndex: Int = 0
@State private var showAnimation: Bool = true
private var scalingFactor: CGFloat = 0.5

var body: some View {

VStack {
Text("Image Detection")
.font(.system(size: 30))
.fontWeight(.heavy)
.padding(.top, 30)

Spacer()

if showAnimation {
image
} else {
image
}

Spacer()

HStack {
Button(action: {
withAnimation {
showAnimation.toggle()
if self.currentIndex >= self.photos.count {
self.currentIndex = self.currentIndex - 1
} else {
self.currentIndex = 0
}
}
}, label: {
Image(systemName: "arrowtriangle.backward.fill")
})
.padding()
.foregroundColor(Color.blue)
.font(.largeTitle)

Spacer()

Button(action: {
withAnimation {
showAnimation.toggle()
if self.currentIndex < self.photos.count - 1 {
self.currentIndex = self.currentIndex + 1
} else {
self.currentIndex = self.photos.count - 1
}
}
}, label: {
Image(systemName: "arrowtriangle.forward.fill")
})
.padding()
.foregroundColor(Color.blue)
.font(.largeTitle)

}
.padding(.horizontal, 50)

Spacer()
}//v
}//body

private var image: some View {
Image(systemName: photos[currentIndex])
.resizable()
.frame(width: 250, height: 250)
.transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))
}
}

Simple transition animation not working in SwiftUI

Transition and animation are different things, there is a view to be shown with transition and there is a view that animates this transition. Also app does not update view, a state should be inside view.

So here is a correct variant:

struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView() // root view of the scene !!
}
}
}

struct ContentView: View {
// state of the view refreshes the view
@State private var isReady: Bool = false

var body: some View {
ZStack { // animating container !!
if isReady {
Color.red
// .transition(.opacity) // probably you wanted this as well
} else {
Color.blue
.transition(.opacity) // << transition here !!
}
}
.onAppear { // << responsible for animation !!
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation {
isReady = true
}
}
}
}
}

SwiftUI animation not working using animation(_:value:)

The difference between animation(_:) and animation(_:value:) is straightforward. The former is implicit, and the latter explicit. The implicit nature of animation(_:) meant that anytime ANYTHING changed, it would react. The other issue it had was trying to guess what you wanted to animate. As a result, this could be erratic and unexpected. There were some other issues, so Apple has simply deprecated it.

animation(_:value:) is an explicit animation. It will only trigger when the value you give it changes. This means you can't just stick it on a view and expect the view to animate when it appears. You need to change the value in an .onAppear() or use some value that naturally changes when a view appears to trigger the animation. You also need to have some modifier specifically react to the changed value.

struct ContentView: View {
@State var isOn = false
//The better route is to have a separate variable to control the animations
// This prevents unpleasant side-effects.
@State private var animate = false

var body: some View {
VStack {
Text("I don't change.")
.padding()
Button("Press me, I do change") {
isOn.toggle()
animate = false
// Because .opacity is animated, we need to switch it
// back so the button shows.
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
animate = true
}
}
// In this case I chose to animate .opacity
.opacity(animate ? 1 : 0)
.animation(.easeIn, value: animate)
.frame(width: 300, height: 400)
// If you want the button to animate when the view appears, you need to change the value
.onAppear { animate = true }
}
}
}

SwiftUI conditional view will not animate/transition

Place your .transition on the container of the views that will switch, not each conditional view. Here's a trivial example from some code I have done (which works).

In the main View that needs to transition conditionally:

import SwiftUI

struct AppWrapperView: View {

@State var showFirstRun:Bool = true

var body: some View {
ZStack {
if (showFirstRun) {
FirstRunView(showFirstRun: $showFirstRun)
} else {
Text("Some other view")
}
}
.transition(.slide)
}
}

Then, somewhere in the view that triggers the change in condition:

import SwiftUI

struct FirstRunView: View {

@Binding var showFirstRun:Bool

var body: some View {

Button(action: {
withAnimation {
self.showFirstRun = false
}
}) {
Text("Done")
}
}
}


Related Topics



Leave a reply



Submit