100 Days of SwiftUI Day 33 Advanced Animations

100 Days of SwiftUI – Day 33

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 .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.

Paul Hudson, Hacking With Swift (@twostraws)
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!

Darryl

Hi! My name is Darryl and this is my personal blog where I write about my journey as I learn programming! You'll also find articles about other things that interest me including games, tech and anime.

Post navigation

Leave a Comment

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

100 Days of SwiftUI – Day 19 – Challenge Day #1

100 Days of SwiftUI – Day 37

100 Days of SwiftUI – Day 76

100 Days of SwiftUI – Day 42