Transition happens when a view is added or removed from the view tree. SwiftUI doesn't directly supports adding or removing view but instead all of that is driven through the state change and combination of if/switch/foreach statements.
Let’s understand this with examples.
We will start with a simple view.
struct DevTechieTransitionsExample: View {
var body: some View {
NavigationStack {
VStack {
Text("SwiftUI")
Text("UIKit")
Text("iOS")
}
.font(.title)
.navigationTitle("DevTechie.com")
}
}
}
Believe it or not but we have a transition in our view. Default transition is the fade transition. We can change transition using combination of AnyTransition and transition modifier.
There are a few types of transitions available for example, scale
, move
, offset
, slide
, and opacity
struct DevTechieTransitionsExample: View {
@State private var toggleVisibility = false
var body: some View {
NavigationStack {
let transition = AnyTransition.move(edge: .leading)
VStack {
if toggleVisibility {
Text("SwiftUI")
.transition(transition)
Text("UIKit")
.transition(transition)
Text("iOS")
.transition(transition)
}
Toggle("Show/Hide", isOn: $toggleVisibility)
}
.font(.title)
.animation(.easeInOut, value: toggleVisibility)
.padding()
.navigationTitle("DevTechie.com")
}
}
}
struct DevTechieTransitionsExample: View {
@State private var toggleVisibility = false
var body: some View {
NavigationStack {
let transition = AnyTransition.scale
VStack {
if toggleVisibility {
Text("SwiftUI")
.transition(transition)
Text("UIKit")
.transition(transition)
Text("iOS")
.transition(transition)
}
Toggle("Show/Hide", isOn: $toggleVisibility)
}
.font(.title)
.animation(.easeInOut, value: toggleVisibility)
.padding()
.navigationTitle("DevTechie.com")
}
}
}
Scale transition also takes the scale amount as a parameter and we can change that to another value.
struct DevTechieTransitionsExample: View {
@State private var toggleVisibility = false
var body: some View {
NavigationStack {
let transition = AnyTransition.scale(scale: 2.0)
VStack {
if toggleVisibility {
Text("SwiftUI")
.transition(transition)
Text("UIKit")
.transition(transition)
Text("iOS")
.transition(transition)
}
Toggle("Show/Hide", isOn: $toggleVisibility)
}
.font(.title)
.animation(.easeInOut, value: toggleVisibility)
.padding()
.navigationTitle("DevTechie.com")
}
}
}
We can change anchor for the scale as well. Scale takes anchor as a parameter so let’s apply anchor.
struct DevTechieTransitionsExample: View {
@State private var toggleVisibility = false
var body: some View {
NavigationStack {
let transition = AnyTransition.scale(scale: 100.0, anchor: .center)
VStack {
if toggleVisibility {
Text("SwiftUI")
.transition(transition)
Text("UIKit")
.transition(transition)
Text("iOS")
.transition(transition)
}
Toggle("Show/Hide", isOn: $toggleVisibility)
}
.font(.title)
.animation(.easeInOut, value: toggleVisibility)
.padding()
.navigationTitle("DevTechie.com")
}
}
}
We can use scale with anchor transition to build an interesting zoom-in/out transition.
struct DevTechieTransitionsExample: View {
@State private var toggleVisibility = false
var body: some View {
NavigationStack {
let transition = AnyTransition.scale(scale: 100.0, anchor: .center)
VStack {
if toggleVisibility {
Image(systemName: "apple.logo")
.font(.largeTitle)
.foregroundStyle(.pink.gradient)
.transition(transition)
}
Toggle("Show/Hide", isOn: $toggleVisibility)
}
.font(.title)
.animation(.easeInOut, value: toggleVisibility)
.padding()
.navigationTitle("DevTechie.com")
}
}
}
We can even slow down the animation by adding animation duration.
struct DevTechieTransitionsExample: View {
@State private var toggleVisibility = false
var body: some View {
NavigationStack {
let transition = AnyTransition.scale(scale: 100.0, anchor: .center)
VStack {
if toggleVisibility {
Image(systemName: "apple.logo")
.font(.largeTitle)
.foregroundStyle(.pink.gradient)
.transition(transition)
}
Toggle("Show/Hide", isOn: $toggleVisibility)
}
.font(.title)
.animation(Animation.easeInOut(duration: 2), value: toggleVisibility)
.padding()
.navigationTitle("DevTechie.com")
}
}
}
We can set offset for transition as well where the view will be shown/hidden from x, y or x and y directions.
Let’s try transition on x-axis with interactiveSpring animation
struct DevTechieTransitionsExample: View {
@State private var toggleVisibility = false
var body: some View {
NavigationStack {
let transition = AnyTransition.offset(x: -500)
VStack {
if toggleVisibility {
Image(systemName: "apple.logo")
.font(.system(size: 100))
.foregroundStyle(.pink.gradient)
.transition(transition)
}
Toggle("Show/Hide", isOn: $toggleVisibility)
}
.font(.title)
.animation(Animation.interactiveSpring(dampingFraction: 0.3), value: toggleVisibility)
.padding()
.navigationTitle("DevTechie.com")
}
}
}
Let’s replace x with y axis and try that out.
struct DevTechieTransitionsExample: View {
@State private var toggleVisibility = false
var body: some View {
NavigationStack {
let transition = AnyTransition.offset(y: -500)
VStack {
if toggleVisibility {
Image(systemName: "apple.logo")
.font(.system(size: 100))
.foregroundStyle(.pink.gradient)
.transition(transition)
}
Toggle("Show/Hide", isOn: $toggleVisibility)
}
.font(.title)
.animation(Animation.interactiveSpring(dampingFraction: 0.3), value: toggleVisibility)
.padding()
.navigationTitle("DevTechie.com")
}
}
}
Let’s apply offset for both x and y axis.
struct DevTechieTransitionsExample: View {
@State private var toggleVisibility = false
var body: some View {
NavigationStack {
let transition = AnyTransition.offset(x: -500, y: -500)
VStack {
if toggleVisibility {
Image(systemName: "apple.logo")
.font(.system(size: 100))
.foregroundStyle(.pink.gradient)
.transition(transition)
}
Toggle("Show/Hide", isOn: $toggleVisibility)
}
.font(.title)
.animation(Animation.interactiveSpring(dampingFraction: 0.3), value: toggleVisibility)
.padding()
.navigationTitle("DevTechie.com")
}
}
}
So far our view’s transition has been single sided, meaning upon removal of the view it goes in the same direction it came from. We can define different transition technique for insertion and removal with the help of asymmetric transition.
struct DevTechieTransitionsExample: View {
@State private var toggleVisibility = false
var body: some View {
NavigationStack {
let transition = AnyTransition.asymmetric(insertion: .move(edge: .top), removal: .opacity)
VStack {
if toggleVisibility {
Image(systemName: "apple.logo")
.font(.system(size: 100))
.foregroundStyle(.pink.gradient)
.transition(transition)
}
Toggle("Show/Hide", isOn: $toggleVisibility)
}
.font(.title)
.animation(Animation.interactiveSpring(dampingFraction: 0.3), value: toggleVisibility)
.padding()
.navigationTitle("DevTechie.com")
}
}
}
We can use combine function with AnyTransition to add another transition on the top of existing one. This helps us build complex transitions.
struct DevTechieTransitionsExample: View {
@State private var toggleVisibility = false
var body: some View {
NavigationStack {
VStack {
let transition = AnyTransition.asymmetric(insertion: .slide, removal: .move(edge: .bottom)).combined(with: .opacity)
if toggleVisibility {
Image(systemName: "carrot")
.font(.system(size: 100))
.foregroundStyle(.pink.gradient)
.transition(transition)
}
Toggle("Show/Hide", isOn: $toggleVisibility)
Image(systemName: "trash.fill")
.font(.system(size: 150))
}
.font(.title)
.animation(Animation.interactiveSpring(dampingFraction: 0.3), value: toggleVisibility)
.padding()
.navigationTitle("DevTechie.com")
}
}
}