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)
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)!
}
}
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
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
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.
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
How to Instantiate a Storyboard from a File Within an iOS Playground
Swift Realm, Load the Pre-Populated Database the Right Way
Subclass.Fetchrequest() Swift 3.0, Extension Not Really Helping 100%
How to Continue Ble Activities Onto Next View Controller
"Cannot Inherit from Non-Open Class" Swift
Why Do I Need to Declare an Optional Value as Nil Explicitly in Struct - Swift
How to Get Unmanaged Object from Realm Query in Swift
Can't Use Concrete Subclass to Implement a Property in Protocol in Swift
Example of Dispatch_Once in Swift
Add Custom Header to Collection View Swift
Uitableview - Multiple Selection and Single Selection
How to Get Day and Month from Date Type - Swift 4
How to Setup a Second Component with a Uipickerview