We’ve arrived at day 33 of the 100 Days of SwiftUI, meaning we have now finished 1/3 of the course. Not too shabby! Yesterday, we learned about animations and how we can use them to enhance our apps. Today, we’re diving even further into animations and focus on a few more advanced techniques.
Controlling the animation stack
During day 23, we learned that the order in which we add modifiers to our views matter. This also applies to animations.
For example, take a look at this code:
.frame(width: 200, height: 200)
.background(enabled ? .blue : .red)
.foregroundColor(.white)
.animation(.default, value: enabled)
.clipShape(RoundedRectangle(cornerRadius: enabled ? 60 : 0))
We’ve added a clipShape
modifier after our animation
modifier. This means that the clipShape
won’t be animated. If we switch it around however, it will be animated just fine!
.frame(width: 200, height: 200)
.background(enabled ? .blue : .red)
.foregroundColor(.white)
.clipShape(RoundedRectangle(cornerRadius: enabled ? 60 : 0))
.animation(.<strong>default</strong>, value: enabled)
Animation gestures
SwiftUI lets us attach gestures to any view
. The effect of these gestures can also be animated.
We’ll dive further into gestures later on, so for today, we’ll be looking at a simple example.
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, id: \.self) { num in
Text(String(letters[num]))
.padding(5)
.font(.title)
.background(enabled ? .blue : .red)
.offset(dragAmount)
.animation(.default.delay(Double(num) / 20), value: dragAmount)
}
}
.gesture(
DragGesture()
.onChanged { dragAmount = $0.translation }
.onEnded { _ in
dragAmount = .zero
enabled.toggle()
}
)
}
}
Showing and hiding views with transitions
One of the most powerful features of SwiftUI is the ability to customize the way views are shown and hidden. Previously you’ve seen how we can use regular
if
conditions to include views conditionally, which means when that condition changes we can insert or remove views from our view hierarchy.Transitions control how this insertion and removal takes place, and we can work with the built-in transitions, combine them in different ways, or even create wholly custom transitions.
Paul Hudson, Hacking With Swift (@twostraws)
@State private var isShowingRed = false
struct ContentView: View {
var body: some View {
VStack {
Button("Tap Me") {
withAnimation {
isShowingRed.toggle()
} }
if isShowingRed {
Rectangle()
.fill(.red)
.frame(width: 200, height: 200)
}
}
}
}
Building custom transitions using ViewModifier
It’s possible – and actually surprisingly easy – to create wholly new transitions for SwiftUI, allowing us to add and remove views using entirely custom animations.
This functionality is made possible by the
Paul Hudson, Hacking With Swift (@twostraws).modifier
transition, which accepts any view modifier we want. The catch is that we need to be able to instantiate the modifier, which means it needs to be one we create ourselves.
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 {
ZStack {
Rectangle()
.fill(.blue)
.frame(width: 200, height: 200)
if isShowingRed {
Rectangle()
.fill(.red)
.frame(width: 200, height: 200)
.transition(.pivot)
}
}
.onTapGesture {
withAnimation {
isShowingRed.toggle()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
And that’s it for day 33! We’ll be back tomorrow to review what we’ve learned about the past days and ensure everything has settled. Time to recharge!
100 Days of SwiftUI – Day 33