I have the following view.
import SwiftUI
struct AppView: View {
@EnvironmentObject var appStore: AppStore
var body: some View {
ZStack {
Color.blue
if .game == self.appStore.appMode {
GameView()
} else if .options == self.appStore.appMode {
OptionsView()
} else {
MenuView()
}
}
}
}
I want to animate the switching between the child views.
I have seen some examples using a state for one switch, but I'm relying on more than one.
I also tried to apply the animation inside a child view with onAppear
and onDisappear
. That works, but when the view is not shown, the onDisappear
does not get executed anymore. Besides, I would like to make it work for all the views.
Is there any way to do this without using multiple states? I would love to use only .transition
and .animation
.
Right now my best solution is this.
import SwiftUI
struct AppView: View {
@EnvironmentObject var appStore: AppStore
var body: some View {
ZStack {
Color.blue
if .game == self.appStore.appMode {
GameView()
.transition(.scale)
.zIndex(1) // to keep the views on top, however this needs to be changed when the active child view changes.
} else if .options == self.appStore.appMode {
OptionsView()
.transition(.scale)
.zIndex(2)
} else {
MenuView()
.transition(.scale)
.zIndex(3)
}
}
}
}
And on every view changer.
.onTapGesture {
withAnimation(.easeInOut(duration: 2)) {
self.appStore.appMode = .game
}
}
SwiftUI has built-in support for animations with its animation() modifier. To use this modifier, place it after any other modifiers for your views, tell it what kind of animation you want, and also make sure you attach it to a particular value so the animation triggers only when that specific value changes.
Use an opacity modifier when you don't want other content to shift around as the view appears or disappears. This modifier is similar to the hidden property in UIKit.
With the following approach you can modify your appMode
as you wish (onAppear, onTapGesture, etc.). Animation duration is, of course, can be set any you wish (as well as kind of transition, actually, however some transitions behaves bad in Preview, and should be tested on Simulator or real device).
Demo: (first blink is just Preview launch, then two transitions for onAppear, then for onTap)
Tested with Xcode 11.4 / iOS 13.4 - works and in Preview and in Simulator.
Code: Important parts marked with comments inline.
var body: some View {
ZStack {
// !! to keep background deepest, `cause it affects removing transition
Color.blue.zIndex(-1)
// !! keep any view in explicit own `if` (don't use `else`)
if .game == self.appStore.appMode {
GameView()
.transition(AnyTransition.scale.animation(.easeInOut(duration: 1)))
}
if .options == self.appStore.appMode {
OptionsView()
.transition(AnyTransition.scale.animation(.easeInOut(duration: 1)))
}
if .menu == self.appStore.appMode {
MenuView()
.transition(AnyTransition.scale.animation(.easeInOut(duration: 1)))
}
}
}
Update: for .slide
(and probably other moving transitions) the change of state should be wrapped into explicit withAnimation
, like below
withAnimation {
self.appStore.appMode = new_mode_here
}
Note: these is one of transitions that is not supported by Preview - test in Simulator.
Example for transition like
...
if .menu == self.appStore.appMode {
Text("MenuView").frame(width: 300, height: 100).background(Color.red)
.transition(AnyTransition.move(edge: .bottom).combined(with: .opacity).animation(.easeInOut(duration: 1)))
}
}
.onTapGesture {
withAnimation {
let next = self.appStore.appMode.rawValue + 1
self.appStore.appMode = next > 2 ? .game : AppStore.AppMode(rawValue: next)!
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With