본문 바로가기

IT/Swift

SwiftUI 튜토리얼 5-1 뷰 애니메이션과 트랜지션

Drawing and Animation - Animating Views and Transitions

 

SwiftUI를 사용하면 애니메이션 효과가 어디에 적용되는지에 상관없이 뷰나 뷰의 상태를 변경하는 개별적인 애니메이션을 적용할 수 있습니다. SwiftUI는 이러한 오버랩 및 인터럽트 가능한 애니메이션 등의 모든 복잡한 결합을 당신을 위해 처리합니다.
이번 튜토리얼에서는 Landmarks 앱을 사용하는 사용자의 하이킹을 추적하는 그래프 애니메이션을 적용합니다. animation(_ :) 수정자를 사용하면 뷰에 애니메이션을 적용하는 것이 얼마나 쉬운 지 알 수 있습니다.
아래 AnimatingViewsAndTransition.zip을 다운로드하셔서 스타터 프로젝트(StartingPoint 폴더)를 사용하여 튜토리얼을 따라해보시거나 완료된 프로젝트(Complete 폴더)를 검토해보실수도 있습니다.

When using SwiftUI, you can individually animate changes to views, or to a view’s state, no matter where the effects are. SwiftUI handles all the complexity of these combined, overlapping, and interruptible animations for you.

In this tutorial, you’ll animate a view that contains a graph for tracking the hikes a user takes while using the Landmarks app. Using the animation(_:) modifier, you’ll see just how easy it is to animate a view.

Download the starter project and follow along with this tutorial, or open the finished project and explore the code on your own.

AnimatingViewsAndTransitions.zip
6.35MB

 

 

 

 

개별적으로 뷰에 애니메이션 적용하기(Add Animations to Individual Views)

뷰에 animation(_:)를 사용할 때 SwiftUI는 뷰의 애니메이션에 관련된 어떤 프로퍼티의 변경사항도 애니메이션으로 표현할 수 있습니다. 색상, 오퍼시티, 로테이션, 사이즈 그리고 보든 애니메이션에 관련된 프로퍼티에 말이죠 

When you use the animation(_:) modifier on a view, SwiftUI animates any changes to animatable properties of the view. A view’s color, opacity, rotation, size, and other properties are all animatable.

 

 

 

 

Step 1

HikeView.swift에서 미리보기를 켜보세요 그리고 우측 상단 화살표 버튼을 눌러서 그래프가 감춰졌다가 나타나는 모습을 테스트해보세요.

이번 튜토리얼을 통해서 각각의 스텝을 미리보기로 경험해보실 수 있습니다.

In HikeView.swift, turn on the live preview and experiment with showing and hiding the graph.

Be sure to use the live preview throughout this tutorial so you can experiment with the results of each step.

 

 

 

 

Step 2

animation(.easeInOut())을 추가하여 버튼 회전에 관한 애니메이션을 활성시킵니다.

path: \.elevation부분에서 type of expression is ambiguous without more context 오류가 발생할 수도 있습니다. 이것은 SwiftUI 버그입니다. 클린빌드를 해보고 DrivedData 폴더를 삭제해도 안 없어집니다. 튜토리얼을 천천히 따라 하시다 보면 자연스럽게 없어지는 오류입니다. 아마도 추측하건대 선언되어있는 부분이 임포트 되는데 시간이 오래 걸린 것 같기도 합니다.

Turn on animation for the button’s rotation by adding animation(.easeInOut()).

import SwiftUI

struct HikeView: View {
    var hike: Hike
    @State private var showDetail = false

    var body: some View {
        VStack {
            HStack {
                HikeGraph(data: hike.observations, path: \.elevation)
                    .frame(width: 50, height: 30)

                VStack(alignment: .leading) {
                    Text(hike.name)
                        .font(.headline)
                    Text(hike.distanceText)
                }

                Spacer()

                Button(action: {
                    self.showDetail.toggle()
                }) {
                    Image(systemName: "chevron.right.circle")
                        .imageScale(.large)
                        .rotationEffect(.degrees(showDetail ? 90 : 0))
                        .padding()
                        .animation(.easeInOut())
                }
            }

            if showDetail {
                HikeDetail(hike: hike)
            }
        }
    }
}

 

 

 

 

Step 3

그래프가 표시될 때 버튼을 크게 하여 다른 애니메이션을 추가합니다.
animation (_ :) 수정자는 뷰 내의 모든 애니메이션에 적용됩니다.

Add another animatable change by making the button larger when the graph is visible.

The animation(_:) modifier applies to all animatable changes within the views it wraps.

import SwiftUI

struct HikeView: View {
    var hike: Hike
    @State private var showDetail = false

    var body: some View {
        VStack {
            HStack {
                HikeGraph(data: hike.observations, path: \.elevation)
                    .frame(width: 50, height: 30)

                VStack(alignment: .leading) {
                    Text(hike.name)
                        .font(.headline)
                    Text(hike.distanceText)
                }

                Spacer()

                Button(action: {
                    self.showDetail.toggle()
                }) {
                    Image(systemName: "chevron.right.circle")
                        .imageScale(.large)
                        .rotationEffect(.degrees(showDetail ? 90 : 0))
                        .scaleEffect(showDetail ? 1.5 : 1)
                        .padding()
                        .animation(.easeInOut())
                }
            }

            if showDetail {
                HikeDetail(hike: hike)
            }
        }
    }
}

 

 

 

 

Step 4

애니메이션 타입을 easeInOut()에서 spring()로 변경합니다.
SwiftUI에는 springfluid 애니메이션뿐만 아니라 사전 정의 또는 custom easing 기본 애니메이션이 포함되어 있습니다. 애니메이션 속도를 조정하거나 애니메이션이 시작되기 전에 지연을 설정하거나 애니메이션이 반복되도록 지정할 수 있습니다.

Change the animation type from easeInOut() to spring().

SwiftUI includes basic animations with predefined or custom easing, as well as spring and fluid animations. You can adjust an animation’s speed, set a delay before an animation starts, or specify that an animation repeats.

import SwiftUI

struct HikeView: View {
    var hike: Hike
    @State private var showDetail = false

    var body: some View {
        VStack {
            HStack {
                HikeGraph(data: hike.observations, path: \.elevation)
                    .frame(width: 50, height: 30)

                VStack(alignment: .leading) {
                    Text(hike.name)
                        .font(.headline)
                    Text(hike.distanceText)
                }

                Spacer()

                Button(action: {
                    self.showDetail.toggle()
                }) {
                    Image(systemName: "chevron.right.circle")
                        .imageScale(.large)
                        .rotationEffect(.degrees(showDetail ? 90 : 0))
                        .scaleEffect(showDetail ? 1.5 : 1)
                        .padding()
                        .animation(.spring())
                }
            }

            if showDetail {
                HikeDetail(hike: hike)
            }
        }
    }
}

 

 

 

 

Step 5

scaleEffect 수정자 바로 위에 다른 애니메이션 수정자를 추가하여 회전에 대한 애니메이션을 끕니다.

Try turning off animation for the rotation by adding another animation modifier just above the scaleEffect modifier.

 

Experiment

spin을 위해 SwiftUI를 사용해보세요!. 다른 애니메이션 효과를 결합해보시고 무엇이 가능한지 테스트해보세요!

Take SwiftUI for a spin. Try combining different animation effects to see what’s possible.

import SwiftUI

struct HikeView: View {
    var hike: Hike
    @State private var showDetail = false

    var body: some View {
        VStack {
            HStack {
                HikeGraph(data: hike.observations, path: \.elevation)
                    .frame(width: 50, height: 30)

                VStack(alignment: .leading) {
                    Text(hike.name)
                        .font(.headline)
                    Text(hike.distanceText)
                }

                Spacer()

                Button(action: {
                    self.showDetail.toggle()
                }) {
                    Image(systemName: "chevron.right.circle")
                        .imageScale(.large)
                        .rotationEffect(.degrees(showDetail ? 90 : 0))
                        .animation(nil)
                        .scaleEffect(showDetail ? 1.5 : 1)
                        .padding()
                        .animation(.spring())
                }
            }

            if showDetail {
                HikeDetail(hike: hike)
            }
        }
    }
}

 

 

 

Step 6

다음 스탭으로 넘어가시기 전에 두개의 animation(_:) 수정자를 제거해주세요 

Remove both animation(_:) modifiers before moving on to the next section.

 

 

 

 

 

 

상태 변화에 따른 애니메이션 주기(Animate the Effects of State Changes)

이제 개별 뷰에 애니메이션을 적용하는 방법을 배웠으므로 이제 상태 값에 변화에 따라 애니메이션을 추가해야 합니다.
여기에서는 사용자가 버튼을 누르고 showDetail 상태 프로퍼티로 토글 할 때 발생하는 모든 변경 사항에 대해 애니메이션을 적용합니다.

Now that you’ve learned how to apply animations to individual views, it’s time to add animations in places where you change your state’s value.

Here, you’ll apply animations to all of the changes that occur when a user taps a button and toggles the showDetail state property.

 

 

 

Step 1

withAnimation 함수로 showDetail.toggle()에 대한 호출을 감쌉니다.(래핑 합니다.)
showDetail 프로퍼티에 영향을받는 두 뷰 (disclosure 버튼 및 HikeDetail 뷰)에 애니메이션 transitions이 적용되었습니다.

Wrap the call to showDetail.toggle() with a call to the withAnimation function.

Both of the views affected by the showDetail property — the disclosure button and the HikeDetail view — now have animated transitions.

import SwiftUI

struct HikeView: View {
    var hike: Hike
    @State private var showDetail = false

    var body: some View {
        VStack {
            HStack {
                HikeGraph(data: hike.observations, path: \.elevation)
                    .frame(width: 50, height: 30)

                VStack(alignment: .leading) {
                    Text(hike.name)
                        .font(.headline)
                    Text(hike.distanceText)
                }

                Spacer()

                Button(action: {
                    withAnimation {
                        self.showDetail.toggle()
                    }
                }) {
                    Image(systemName: "chevron.right.circle")
                        .imageScale(.large)
                        .rotationEffect(.degrees(showDetail ? 90 : 0))
                        .scaleEffect(showDetail ? 1.5 : 1)
                        .padding()
                }
            }

            if showDetail {
                HikeDetail(hike: hike)
            }
        }
    }
}

 

 

 

애니메이션을 느리게 하여 SwiftUI 애니메이션이 어떻게 중단되는지 확인해보세요

Slow down the animation to see how SwiftUI animations are interruptible.

 

Step 2

withAnimation 함수에 4초 길이의 기본 애니메이션을 전달합니다.
동일한 종류의 애니메이션을 animation(_ :) 수정자에 전달한 withAnimation 함수에 전달할 수 있습니다.

Pass a 4-second long basic animation to the withAnimation function.

You can pass the same kinds of animations to the withAnimation function that you passed to the animation(_:) modifier.

import SwiftUI

struct HikeView: View {
    var hike: Hike
    @State private var showDetail = false

    var body: some View {
        VStack {
            HStack {
                HikeGraph(data: hike.observations, path: \.elevation)
                    .frame(width: 50, height: 30)

                VStack(alignment: .leading) {
                    Text(hike.name)
                        .font(.headline)
                    Text(hike.distanceText)
                }

                Spacer()

                Button(action: {
                    withAnimation(.easeInOut(duration: 4)) {
                        self.showDetail.toggle()
                    }
                }) {
                    Image(systemName: "chevron.right.circle")
                        .imageScale(.large)
                        .rotationEffect(.degrees(showDetail ? 90 : 0))
                        .scaleEffect(showDetail ? 1.5 : 1)
                        .padding()
                }
            }

            if showDetail {
                HikeDetail(hike: hike)
            }
        }
    }
}

 

 

 

 

Step 3

애니메이션 중반 그래프 뷰를 열고 닫는 방법을 테스트해보세요.

Experiment with opening and closing the graph view mid-animation.

 

 

 

 

 

Step 4

다음 섹션을 계속하기 전에 withAnimation 함수 호출에서 느린 애니메이션(.easeInOut(duration: 4))을 제거하세요.

Remove the slow animation from the withAnimation function call before continuing to the next section.

import SwiftUI

struct HikeView: View {
    var hike: Hike
    @State private var showDetail = false

    var body: some View {
        VStack {
            HStack {
                HikeGraph(data: hike.observations, path: \.elevation)
                    .frame(width: 50, height: 30)

                VStack(alignment: .leading) {
                    Text(hike.name)
                        .font(.headline)
                    Text(hike.distanceText)
                }

                Spacer()

                Button(action: {
                    withAnimation {
                        self.showDetail.toggle()
                    }
                }) {
                    Image(systemName: "chevron.right.circle")
                        .imageScale(.large)
                        .rotationEffect(.degrees(showDetail ? 90 : 0))
                        .scaleEffect(showDetail ? 1.5 : 1)
                        .padding()
                }
            }

            if showDetail {
                HikeDetail(hike: hike)
            }
        }
    }
}