Morpheus
Morpheus

Reputation: 1289

2-step animated transition in SwiftUI

I'd like to make a custom view transition in SwiftUI, animated in 2 steps:

Presenting child

  1. Fade out parent (opacity to 0)
  2. (Then) fade in child and slide from bottom

Dismissing child

  1. Fade out child and slide to bottom
  2. (Then) fade in parent

Demo (created artificially)

Demo

Base code:

struct TransitionTestView: View {
    @State var presented = false

    var body: some View {
        ZStack {
            if presented {
                Button("Hide", action: { presented = false })
                    .foregroundColor(.black)
                    .font(.largeTitle)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(.purple)
            }
            else {
                Button("Show", action: { presented = true })
                    .foregroundColor(.black)
                    .font(.largeTitle)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(.orange)
            }
        }
        .padding(30)
    }
}

I've experimented with:

But I couldn't make it work. Any help appreciated, thanks a lot!

Upvotes: 0

Views: 569

Answers (1)

Asperi
Asperi

Reputation: 257533

A possible approach can be using transaction transformation by injecting additional delay to main animation for each phase.

Tested with Xcode 13.4 / iOS 15.5

demo

Main part:

ZStack {
    VStack {  // << animating container
        if presented {
            Button("Hide", action: { presented = false })
                .foregroundColor(.black)
                .font(.largeTitle)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(.purple)
                .transition(.move(edge: .bottom))
        }
    }
    .transaction { // << transform animation for direct transition
        $0.animation = $0.animation?.delay(presented ? 1.2 : 0)
    }

    VStack { // << animating container
        if !presented {
            Button("Show", action: { presented = true })
                .foregroundColor(.black)
                .font(.largeTitle)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(.orange)
                .transition(.opacity)
        }
    }
    .transaction { // << transform animation for reverse transition
        $0.animation = $0.animation?.delay(presented ? 0 : 1.2)
    }
}
.animation(.linear(duration: 1), value: presented)   // << main !!

Test module on GitHub

Upvotes: 2

Related Questions