Search code examples
swiftxcodeanimationswiftuitransition

Different transitions for different elements of a view when it's presented in SwiftUI


so I'm trying to show different elements in different ways when a view is presented in SwiftUI (One is a slide in from the leading edge and the other element is a slide up from the bottom of the screen). My basic view structure is as follows:

struct ViewName: View {
  @ObservedObject var appState: AppState //this is just a class that tracks the state of app variables, in my case it holds a variable called 'showView' that indicates whether or not to show the view.
  var body: some View {
    ZStack {
      Color.white
        .edgesIgnoringSafeArea(.all)
      VStack {
        Text("Test")
      }.transition(AnyTransition.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing)))
      VStack {
        Spacer()
        HStack {
          Text("Test2")
          Spacer()
          Text("Test3")
        }
      }.transition(AnyTransition.move(edge: .bottom))
    }
  }
}

Elsewhere, I have the view initialized with something like:

if appState.showView {
  ViewName(appState: appState)
}

and a button that changes whether the view is presented:

Button(action: {
  withAnimation {
    appState.showView.toggle()
  }
}, label: { 
  Text("Click me") 
})

It seems like Swift doesn't know what to do with the two transitions though, and it sort of defaults them both to a fade-in opacity transition. Not sure how to fix this. Any help much appreciated!


Solution

  • The problem is that you only have 1 if appState.showView {.

    if appState.showView {
        ViewName(appState: appState)
    }
    

    As a result, SwiftUI only animates the entire ViewName in and out, with the default fade transition (because you didn't specifify one).

    Instead, you need to use if appState.showView { on each separate element that you want to animate.

    class AppState: ObservableObject {
        @Published var showView = false
    }
    
    struct ContentView: View {
        @StateObject var appState = AppState()
        var body: some View {
            VStack {
                Button(action: {
                    withAnimation {
                        appState.showView.toggle()
                    }
                }, label: {
                    Text("Click me")
                })
                
                ViewName(appState: appState) /// just make this always showing
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.green)
        }
    }
    
    struct ViewName: View {
        @ObservedObject var appState: AppState
        var body: some View {
            ZStack {
                if appState.showView { /// need this, so `ViewName` will be invisible when `appState.showView` is false
                    Color.white
                    .edgesIgnoringSafeArea(.all)
                    /// optional: add a transition here too
                    /// by default, it will fade
                }
                
                if appState.showView { /// need this!
                    VStack {
                        Text("Test")
                    }
                    .zIndex(1) /// needed for removal transition
                    .transition(AnyTransition.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing)))
                }
                
                if appState.showView { /// need this!
                    VStack {
                        Spacer()
                        HStack {
                            Text("Test2")
                            Spacer()
                            Text("Test3")
                        }
                    }
                    .zIndex(2) /// needed for removal transition
                    .transition(AnyTransition.move(edge: .bottom))
                }
            }
        }
    }
    
    Transitions work