4

I want to be able to use a different transition depending on the state transition. For example, if I go from .one => .three, I want to fade out ViewOne on removal. If I go from .one => .two, I want to move ViewOne to the left on removal. Right now I have something like this (which clearly, only works for a transition from .one => .two):

ZStack {
    if state == .one {
        ViewOne()
            .transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))
    }

    if state == .two {
        ViewTwo()
            .transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))
    }

    if state == .three {
        ViewThree()
            .transition(.asymmetric(insertion: .opacity, removal: .identity))
    }
}

How can I dynamically change the transition based on the state?

UPDATE: Here's a more complete example:

enum Screen {
    case susi, onboarding, home
}

struct ContentView: View {
    @State var screen: Screen = .susi
    @State var transition: AnyTransition = .asymmetric(insertion: .identity, removal: .move(edge: .leading))
    
    var body: some View {
        ZStack {
            if screen == .susi {
                ViewOne()
                    .transition(transition)

            }
            if screen == .onboarding {
                ViewTwo()
                    .transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))
            }
            if screen == .home {
                ViewThree()
                    .transition(.asymmetric(insertion: .opacity, removal: .identity))
            }
        }
        .onAppear {
            DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                withAnimation(.default) {
                    self.transition = .asymmetric(insertion: .identity, removal: .opacity)
                    self.screen = .home
                }
            }
        }
    }
}
Russ d'Sa
  • 93
  • 4
  • Can you show code for the state? – aheze Apr 07 '21 at 01:59
  • 1
    I added a complete example, illustrating the desired (but not working) behavior. – Russ d'Sa Apr 07 '21 at 02:39
  • Does this answer your question https://stackoverflow.com/questions/61340717/create-a-transition-whose-removal-depends-on-a-state-value-when-the-view-is-rem? – Asperi Apr 07 '21 at 03:45
  • 2
    It's a very good question. As far as I understand, the transition is defined to the view when the view is created, hence changing the transition after the view is created would actually trigger the `removal` transition of the old view (which is the one defined before changing it to fading or moving) hence making it impossible to update the transition property of a view until the next removal. I am curios for a viable solution, too. – Cuneyt Apr 07 '21 at 03:54

1 Answers1

2

For those reading this with the same issue, one solution was toggling the id property of a view. Some searching around SO lead me to this technique, which apparently resets the animations for a view.

import SwiftUI

enum Screen {
    case susi, onboarding, home
}

struct ContentView: View {
    @State var screen: Screen = .susi
    @State var transition: AnyTransition = .move(edge: .leading)
    @State var viewId = UUID().uuidString
    
    var body: some View {
        ZStack {
            if screen == .susi {
                ViewOne()
                    .transition(transition)

            }
            if screen == .onboarding {
                ViewTwo()
                    .transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .trailing)))
            }
            if screen == .home {
                ViewThree()
                    .transition(.asymmetric(insertion: .opacity, removal: .move(edge: .bottom)))
            }
        }
        .id(viewId)
        .onAppear {
            DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                withAnimation(.default) {
                    self.screen = .onboarding
                }
                
                DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                    withAnimation(.default) {
                        self.screen = .susi
                    }
                    
                    DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                        self.transition = .opacity
                        self.viewId = UUID().uuidString
                        
                        withAnimation(.default) {
                            self.screen = .home
                        }
                        
                        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                            self.transition = .move(edge: .top)
                            self.viewId = UUID().uuidString
                            
                            withAnimation(.default) {
                                self.screen = .susi
                            }
                            
                            DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                                self.transition = .move(edge: .leading)
                                self.viewId = UUID().uuidString
                                
                                withAnimation(.default) {
                                    self.screen = .onboarding
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

I ended up instead modifying the id of ViewOne instead of the entire ZStack, out of fear that doing so would yield some unexpected SwiftUI quirks down the road. Same technique, just applying it to a different view.

Russ d'Sa
  • 93
  • 4