How to Animate Transition Between Views in Swiftui

How to animate transition between views in SwiftUI?

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

How to transition between two custom views based on state change?

You can use a .transition on your elements and withAnimation when you change the value that affects their state:

enum ViewToShow {
case one
case two
}

struct ContentView: View {
@State var viewToShow : ViewToShow = .one

var body: some View {
switch viewToShow {
case .one:
DetailView(title: "one", color: .red)
.transition(.opacity.combined(with: .move(edge: .leading)))
case .two:
DetailView(title: "two", color: .yellow)
.transition(.opacity.combined(with: .move(edge: .top)))
}
Button("Toggle") {
withAnimation {
viewToShow = viewToShow == .one ? .two : .one
}
}
}
}

struct DetailView : View {
var title: String
var color : Color

var body: some View {
Text(title)
.background(color)
}
}

SwiftUI: Animating a transition between two views in a WindowGroup not working

There is no animation modifier, so no animation, try to wrap it in Group or some container (however more appropriate, from design perspective, to move all this into ContentView and separate alternates MainView and SettingsView there)

var body: some Scene {
WindowGroup {
Group { // or VStack
if settings.displaySettings {
SettingsView()
.environmentObject(settings)
.transition(.slide)
} else {
ContentView()
.environmentObject(settings)
.transition(.slide)
}
}
.animation(.default, value: settings.displaySettings)
}
}

SwiftUI - Animate view transition and position change at the same time

You can just change the height as it animates.

Code version #1

This will not fade and appears inside the yellow rectangle.

Code:

struct ContentView: View {
@State var showingSubview = false

var body: some View {
VStack(spacing: 0) {
Button("Show Subview") {
withAnimation(.easeInOut(duration: 2)) {
showingSubview.toggle()
}
}

Text("Subview")
.padding()
.background(Color.green)
.padding(.top)
.frame(height: showingSubview ? nil : 0, alignment: .top)
.clipped()
}
.padding()
.background(Color.yellow)
.offset(x: showingSubview ? 150 : 0, y: 0)
}
}

Result #1

Result 1

Code version #2

This version will fade out and appear at bottom edge, as your GIF shows.

Code:

struct ContentView: View {
@State var showingSubview = false

var body: some View {
VStack(spacing: 0) {
Button("Show Subview") {
withAnimation(.easeInOut(duration: 2)) {
showingSubview.toggle()
}
}

Text("Subview")
.padding()
.background(Color.green)
.padding(.top)
.frame(height: showingSubview ? nil : 0, alignment: .top)
.padding(.bottom)
.background(Color.yellow)
.clipped()
.opacity(showingSubview ? 1 : 0)
}
.padding([.horizontal, .top])
.background(Color.yellow)
.padding(.bottom)
.offset(x: showingSubview ? 150 : 0, y: 0)
}
}

Result #2

Result 2

How to animate a view transition on a conditional view?

The .animation modifier should be applied to a container owning conditional view, so it could animate appear/disapper transition, like

    VStack {

if S1 {
VX("V1")
.transition(transition)
}
// ... other code
}
.animation(.easeOut, value: S1) // < here !!

Animate a view from the side in SwiftUI

You can use a .transition() modifier on the circle, and the .blur() modifier on the other views that are not the circle, combined with .animation().

Also, on the views that are "not circle", you need to add another modifier: .allowsHitTesting(), to avoid having the user interacting with the views that are blurred.

Remember that, when you trigger someState, you must use the .withAnimation() closure, otherwise the circle will not slide in.

Here's how the code looks like (I added some things on the ZStack just to provide an example). I also made the someState variable a boolean for convenience in the example, but in your case you can just check for the enum.

CircleView

struct CircleView: View {
@Binding var someState: Bool
var body: some View {
Circle()
.foregroundColor(.red)
.overlay(Text("All is blur"))
.onTapGesture {
someState.toggle()
}

// These modifiers are necessary to animate the circle
.transition(.move(edge: .trailing))
.animation(.easeInOut, value: someState)
}
}

Another view

    @State private var someState = false
var body: some View {
GeometryReader { geo in
ZStack {
VStack {
Button {

// Without this closure, the circle does not slide in
withAnimation {
someState.toggle()
}
} label: {
Text("Show circle")
}
.padding()

Spacer()

Text("Bottom")
.padding()
}

// This modifier blurs the view that is not the circle
.blur(radius: someState ? 5 : 0)

// This modifier animates the blur effect
.animation(.easeInOut, value: someState)

// This is necessary to block the view from accepting user taps
// when the circle is showing
.allowsHitTesting(!someState)

if someState {
CircleView(someState: $someState)

// Stop the circle half-way
.offset(x: UIScreen.main.bounds.width / 2, y: 0)
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.ignoresSafeArea()
}
}

SwiftUI transitions between views, no transition

Put it condition into animatable container, like

WindowGroup {
ZStack { // << here
if appState.isGameActive == false {
MenuView()
.environmentObject(appState)
.transition(.move(edge: .trailing))
} else {
GameView(difficulity: 1)
.transition(.move(edge: .trailing))
}
}
.animation(.default, value: appState.isGameActive) // << here !!
}

*however I would recommend to separate everything related to views into root view so scene would look like

WindowGroup {
ContentView() // << this !!
}

How to animate transition when adding view to hierarchy in SwiftUI

The solution is to add instead animation to container. Tested with Xcode 12 / iOS 14.

demo

struct Popovers : View {

@State var popovers : [AnyView] = []

var body : some View {
Button("Add a view ...") {
withAnimation {
popovers += [new()]
}
}
.blur(radius: 0 < popovers.count ? 8 : 0)
.overlay(ZStack {
ForEach(0..<self.popovers.count, id: \.self) { i in
popovers[i]
.frame(maxWidth: .infinity, maxHeight: .infinity)
.blur(radius: (i+1) < popovers.count ? 8 : 0)
.transition(.move(edge: .trailing))
}
}.animation(.default)) // << add animation to container
}

func new() -> AnyView {
let popover = popovers.count

return AnyView.init(
VStack(spacing: 64) {

Button("Close") {
_ = popovers.removeLast()
}
.font(.largeTitle)
.padding()

Button("Add") {
popovers += [new()]
}
.font(.largeTitle)
.padding()

Text("This is popover #\(popover)")
.font(.title)
.foregroundColor(.white)
.fixedSize()

}
.background(Color.init(hue: 0.65-(Double(3*popover)/100.0), saturation: 0.3, brightness: 0.9).opacity(0.98))
)
}

}


Related Topics



Leave a reply



Submit