Implicit animations means we define the animation we want, and let SwiftUI take care of the rest. We can animation views, or we can animation state through a binding.
We can add animation to a view.
struct ContentView: View {
@State private var animationAmount: CGFloat = 1
var body: some View {
Button("Tap Me") {
self.animationAmount += 1
}
.padding(50)
.background(Color.red)
.foregroundColor(.white)
.clipShape(Circle())
.scaleEffect(animationAmount)
.blur(radius: (animationAmount - 1) * 3)
.animation(.default)
}
}
And we can add animation to a binding.
struct ContentView: View {
@State private var animationAmount: CGFloat = 1
var body: some View {
VStack {
Stepper("Scale amount", value: $animationAmount.animation(), in: 1...10)
Button("Tap Me") {
self.animationAmount += 1
}
.padding(40)
.background(Color.red)
.foregroundColor(.white)
.clipShape(Circle())
.scaleEffect(animationAmount)
}
}
}
Explicit animations are where we are explicit about the animation we want when some state change occurs. It s not attached to a binding. And it's not attached to a view. It's just us explicitly asking for a particulr animation to occur because of a state change.
And we do that using withAnimation()
.
struct ContentView: View {
@State private var animationAmount = 0.0
var body: some View {
Button("Tap Me") {
withAnimation {
self.animationAmount += 360
}
}
.padding(50)
.background(Color.red)
.foregroundColor(.white)
.clipShape(Circle())
.rotation3DEffect(.degrees(animationAmount), axis: (x: 0, y: 1, z: 0))
}
}
withAnimation(.interpolatingSpring(stiffness: 5, damping: 1)) {
self.animationAmount += 360
}
If you don't set the view modifer after all the effects changes you want to see, you won't get them. So add the modifier at the end of the view to get them all.
Button("Tap Me") {
self.enabled.toggle()
}
.frame(width: 200, height: 200)
.background(enabled ? Color.blue : Color.red)
.animation(.default)
.foregroundColor(.white)
.clipShape(RoundedRectangle(cornerRadius: enabled ? 60 : 0))
.animation(.interpolatingSpring(stiffness: 10, damping: 1))
Dragging a card around the screen and then letting it snap back to its original position.
Here is the card with no animation.
struct ContentView: View {
@State private var dragAmount = CGSize.zero
var body: some View {
LinearGradient(gradient: Gradient(colors: [.yellow, .red]), startPoint: .topLeading, endPoint: .bottomTrailing)
.frame(width: 300, height: 200)
.clipShape(RoundedRectangle(cornerRadius: 10))
.offset(dragAmount)
.gesture(
DragGesture()
.onChanged { self.dragAmount = $0.translation }
.onEnded { _ in self.dragAmount = .zero }
)
}
}
Here we add an implicity animation that will animate the drag and the release.
.gesture(
DragGesture()
.onChanged { self.dragAmount = $0.translation }
.onEnded { _ in self.dragAmount = .zero }
)
.animation(.spring())
To explicitly set the animation, remove the .animation
modifier and explicitly change .onEnded
to this.
.onEnded { _ in
withAnimation(.spring()) {
self.dragAmount = .zero
}
}
Now the card will follow your drag immediately (because it is not being animated), but will animate when released.
Can combine these in interesting ways like this.
struct ContentView: View {
let letters = Array("Hello SwiftUI")
@State private var enabled = false
@State private var dragAmount = CGSize.zero
var body: some View {
HStack(spacing: 0) {
ForEach(0..<letters.count) { num in
Text(String(self.letters[num]))
.padding(5)
.font(.title)
.background(self.enabled ? Color.blue : Color.red)
.offset(self.dragAmount)
.animation(Animation.default.delay(Double(num) / 20))
}
}
.gesture(
DragGesture()
.onChanged { self.dragAmount = $0.translation }
.onEnded { _ in
self.dragAmount = .zero
self.enabled.toggle()
}
)
}
}
Here is an example of how to make the red square appear with a scaled transition.
struct ContentView: View {
@State private var isShowingRed = false
var body: some View {
VStack {
Button("Tap Me") {
withAnimation {
self.isShowingRed.toggle()
}
}
if isShowingRed {
Rectangle()
.fill(Color.red)
.frame(width: 200, height: 200)
.transition(.scale)
}
}
}
}
Here's another.
.transition(.asymmetric(insertion: .scale, removal: .opacity))
struct CornerRotateModifier: ViewModifier {
let amount: Double
let anchor: UnitPoint
func body(content: Content) -> some View {
content.rotationEffect(.degrees(amount), anchor: anchor).clipped()
}
}
extension AnyTransition {
static var pivot: AnyTransition {
.modifier(
active: CornerRotateModifier(amount: -90, anchor: .topLeading),
identity: CornerRotateModifier(amount: 0, anchor: .topLeading)
)
}
}
struct ContentView: View {
@State private var isShowingRed = false
var body: some View {
VStack {
Button("Tap Me") {
withAnimation {
self.isShowingRed.toggle()
}
}
if isShowingRed {
Rectangle()
.fill(Color.red)
.frame(width: 200, height: 200)
.transition(.pivot)
}
}
}
}