Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to animate transition between views in SwiftUI?

Tags:

swift

swiftui

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
    }
}
like image 786
cakeheart Avatar asked Mar 21 '20 05:03

cakeheart


People also ask

How do I use animation in SwiftUI?

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.

How do you make a view disappear in SwiftUI?

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.


1 Answers

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)

demo

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

demo2

like image 71
Asperi Avatar answered Oct 07 '22 22:10

Asperi