Russ d'Sa
Russ d'Sa

Reputation: 103

Dynamic transitions in SwiftUI

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
                }
            }
        }
    }
}

Upvotes: 5

Views: 914

Answers (1)

Russ d'Sa
Russ d'Sa

Reputation: 103

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.

Upvotes: 2

Related Questions