SwiftUI Transitions have different implementation?

Viewed 1644

I have been testing how transitions work based on SwiftUI-labs but I found that transitions should not all be implemented in the same way. Based on the previous article:

Note that since XCode 11.2, transitions no longer work with implicit animations

Thing is, that I found something strange. Some transitions work fine when using either implicit animation OR by associating an animation to it. So, what animation should be used? Well, it depends. On what? I don't know. I'm hoping anyone can help me explaining all this.

In the following test, I have created 5 views, each one associated with a different transition: .opacity,.scale, .move, .slide, and combined(.opacity and .move). Here are my findings:

NOTE: I'M USING XCODE 11.4 AND THE SIMULATOR!

Transitions using implicit animation

Only .move and .slide transitions work OK (removal and insertion).

enter image description here

Transitions using explicit animation

Removal and insertion animation works OK for all transitions.

enter image description here

Associating an animation to each transition

Only .scale and .opacity transitions work OK (removal and insertion).

enter image description here

From my point of view, not having a standard way of implementing transitions complicates things, moreover when combining transitions (.combined).

Am I missing something about the implementation? Here's my code:

struct TestAnimation: View {
    @State var show : Bool = true
    var colors : [Color] = [.orange, .yellow, .green, .blue, .pink]

    var body: some View {
        VStack(alignment: .leading) {
            Spacer()

            Color.purple
                .frame(height: 100)
                .overlay(
                    Text("Tap Me!").foregroundColor(.white))
                .onTapGesture {
                    // (#1) implicit animation
                    self.show.toggle()

                    // (#2) explicit animation
                    /*withAnimation(Animation.easeInOut(duration: 1)) {
                        self.show.toggle()
                    }*/

                    // (#3) associate an animation with a transition
                    //self.show.toggle()
            }

            HStack {
                if show {
                    Rectangle()
                        .fill(colors[0])
                        .frame(width: 70, height: 100)
                        .overlay(Text("opacity"))
                        .transition(AnyTransition.opacity) // (#1) - doesn't animate, only removes/inserts the view
                        .animation(.easeInOut(duration: 1)) // (#1) 
                        //.transition(AnyTransition.opacity) // (#2) - only animates the removal, not the insertion
                        //.transition(AnyTransition.opacity.animation(.easeInOut(duration: 1))) // (#3) - animates removal and insertion

                    Rectangle()
                        .fill(colors[1])
                        .frame(width: 70, height: 100)
                        .overlay(Text("scale"))
                        .transition(AnyTransition.scale) // (#1) - doesn't animate, only removes/inserts the view
                        .animation(.easeInOut(duration: 1)) // (#1) 
                        //.transition(AnyTransition.scale) // (#2) - only animates the removal, not the insertion
                        //.transition(AnyTransition.scale.animation(.easeInOut(duration: 1))) // (#3) - animates removal and insertion

                    Rectangle()
                        .fill(colors[2])
                        .frame(width: 70, height: 100)
                        .overlay(Text("move"))
                        .transition(AnyTransition.move(edge: .bottom)) // (#1) - animates removal and insertion
                        .animation(.easeInOut(duration: 1)) // (#1) 
                        //.transition(AnyTransition.move(edge: .bottom)) // (#2) - only animates the removal, not the insertion
                        //.transition(AnyTransition.move(edge: .bottom).animation(.easeInOut(duration: 1))) // (#3) - doesn't animate, only removes/inserts the view

                    Rectangle()
                        .fill(colors[3])
                        .frame(width: 70, height: 100)
                        .overlay(Text("slide"))
                        .transition(AnyTransition.slide) // (#1) - animates removal and insertion
                        .animation(.easeInOut(duration: 1)) // (#1) 
                        //.transition(AnyTransition.slide) // (#2) - only animates the removal, not the insertion
                        //.transition(AnyTransition.slide.animation(.easeInOut(duration: 1))) // (#3) - doesn't animate, only removes/inserts the view

                    Rectangle()
                    .fill(colors[4])
                    .frame(width: 70, height: 100)
                    .overlay(Text("op&mv"))
                    .transition(AnyTransition.opacity.combined(with: .move(edge: .bottom))) // (#1) - doesn't animate, only removes/inserts the view
                    .animation(.easeInOut(duration: 1)) // (#1) 
                    //.transition(AnyTransition.opacity.combined(with: .move(edge: .bottom))) // (#2) - only animates removal, not the insertion
                        //.transition(AnyTransition.opacity.combined(with: .move(edge: .bottom)).animation(.easeInOut(duration: 1))) // (#3) - animates removal (it's not smooth), and only animates opacity on insertion
                }
            }
            .frame(height: 100)
            .padding(7)
                .border(Color.gray)
        }
    }

}
0 Answers
Related