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

Viewed 322

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!

1 Answers

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
Related