봄날은 갔다. 이제 그 정신으로 공부하자

5. SwiftUI Tutorial - animation 본문

iOS Tip

5. SwiftUI Tutorial - animation

길재의 그 정신으로 공부하자 2022. 3. 16. 22:22

이 글은 swiftUI Tutorial 중 5번째 항목인 "Animating views and Transitions" 내용을 기반으로 설명합니다.

전체 소스 및 내용이 궁금하신 분은 아래 링크를 참고해주세요.

https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions

 

버튼에 Animation 효과 추가하기

아래 이미지 우측 화살표 버튼을 클릭하면

아래 이미지와 같이 1.5배 커지면서 화살표가 90도 돌아가는 애니메이션을 예를 들어 설명하도록 하겠습니다.

 

위 조건을 만족 시키기 위해 아래와 같이 버튼을 생성해 줍니다.

* 전체 소스는 위 링크를 참고해주세요.

@State private var showDetail = false

...

Button {
    showDetail.toggle()
 } label: {
     Label("Graph", systemImage: "chevron.right.circle")
         .labelStyle(.iconOnly)
         .imageScale(.large)
         .rotationEffect(.degrees(showDetail ? 90 : 0))
         .scaleEffect(showDetail ? 1.5 : 1)
         .padding()
         .animation(.easeInOut, value: showDetail)
 }

 

rotationEffect()

위 소스 중 버튼의 회전을 담당하는 부분은 rotationEffect() 함수이며 이 함수로 인해 사용자가 버튼을 클릭할 때마다 90도를 회전시켰다가 다시 원위치 시켰다를 반복합니다.

 

scaleEffect()

버튼의 크기를 조절하는 함수는 scaleEffect() 함수로 이 함수로 인해 사용자가 버튼을 클릭할 때마다 아이콘의 크기가 늘어났다 줄어들었다를 반복합니다. 위 두 개 함수만 사용한다면 사용자가 버튼을 눌렀을 때 그냥 이미지가 돌아가고 아이콘의 크기가 변하기만 하지 애니메이션 효과가 적용되지는 않습니다.

 

animation()

애니메이션 효과를 적용하기 위해서는 animation() 함수를 사용해야 합니다.

위 예시에서는 애니메이션 효과로 easeInOut를 사용하였습니다.

easeInOut 외에 지원하는 애니메이션 효과에 대해서는 아래 "Animation struct 정의"에서 자세히 설명합니다.

 

withAnimation()

애니메이션의 유지 시간을 개발자가 정할 수 있는데 이를 사용하기 위해서는 아래와 같이 withAnimation()함수를 사용해야 합니다.

Button {
   withAnimation(.easeInOut(duration: 4)) {
       showDetail.toggle()
    }
} label: {
     Label("Graph", systemImage: "chevron.right.circle")
         .labelStyle(.iconOnly)
         .imageScale(.large)
         .rotationEffect(.degrees(showDetail ? 90 : 0))
         .scaleEffect(showDetail ? 1.5 : 1)
         .padding()
}

 

Animation struct 정의

SwiftUI에 정의된 Animation을 보면 아래와 같이 iOS는 버전 13.0부터 지원됨을 알 수 있으며

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen public struct Animation : Equatable {

    /// Returns a Boolean value indicating whether two values are equal.
    ///
    /// Equality is the inverse of inequality. For any values `a` and `b`,
    /// `a == b` implies that `a != b` is `false`.
    ///
    /// - Parameters:
    ///   - lhs: A value to compare.
    ///   - rhs: Another value to compare.
    public static func == (lhs: Animation, rhs: Animation) -> Bool
}

 

또 아래와 같은 여러 애니메이션 효과를 기본적으로 지원함을 알 수 있으며,

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension Animation {

    public static func easeInOut(duration: Double) -> Animation

    public static var easeInOut: Animation { get }

    public static func easeIn(duration: Double) -> Animation

    public static var easeIn: Animation { get }

    public static func easeOut(duration: Double) -> Animation

    public static var easeOut: Animation { get }

    public static func linear(duration: Double) -> Animation

    public static var linear: Animation { get }

    public static func timingCurve(_ c0x: Double, _ c0y: Double, _ c1x: Double, _ c1y: Double, duration: Double = 0.35) -> Animation
}

 

SwiftUI Animation은 delay와 speed를 조절하는 함수 및 반복 횟수를 지정하는 repeatCount()와 repeatForever() 함수 또한 지원하고 있습니다.

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension Animation {

    public func delay(_ delay: Double) -> Animation
}

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension Animation {

    /// Returns an animation that has its speed multiplied by `speed`. For
    /// example, if you had `oneSecondAnimation.speed(0.25)`, it would be at 25%
    /// of its normal speed, so you would have an animation that would last 4
    /// seconds.
    public func speed(_ speed: Double) -> Animation
}

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension Animation {

    public func repeatCount(_ repeatCount: Int, autoreverses: Bool = true) -> Animation

    public func repeatForever(autoreverses: Bool = true) -> Animation
}

 

Animation 좀 더 풍성하게 사용하기

SwiftUI Animation에서 많은 것을 지원해주어도 개발시에는 추가로 애니메이션 효과를 만들어야할 때가 있습니다.

스프링 효과를 2배속으로 주는 애니메이션을 만들어 보겠습니다.

만들어진 코드는 아래와 같습니다.

extension Animation {
    static func ripple() -> Animation {
        Animation.spring(dampingFraction: 0.5).speed(2)
    }
}
// spring()함수에서 지원하는 파라미터 설명은 다음과 같습니다.
//    - response: 스프링의 강성을 나타내는 값으로 0이면 무한히 단단해져 스프핑 효과 없음.
//    - dampingFraction: 애니메이션되는 값에 적용된 드래그의 크기로 0에서 1사이의 값을 사용하며 0은 절대 멈추지 않음.
//    - blendDuration: 스프링의 response에 대한 변경 사항을 보간할 기간(단위:sec)입니다.
// speed는 배속으로 기본값이 1이며, 2는 2초가 아닌 2배속 입니다.

 

AnyTransition  좀 더 풍성하게 사용하기

애니메이션처럼 Transition도 개발자가 View가 보여지는 방식을 지정할 수 도 있습니다.

아래 코드와 같이 transition()함수에 slide 트랜지션을 사용하면 View가 보여질때는 leading이 사라질때는 trailing이 적용되지만

HikeDetail(hike: hike).transition(.slide)

 

이런거 싫고 위에서 나타났다 위로 사라지는 것을 원할 때는 transition() 함수에 top을 직접적으로 사용할 수 없으므로 아래와 같이 작업을 해주어야 합니다.

HikeDetail(hike: hike).transition(.moveAndFade)

...

extension AnyTransition {
    static var moveAndFade: AnyTransition {
        AnyTransition.move(edge: .top)
    }
}

 

나타날때와 사라질때는 구분해 처리하고자 할 때는 아래와 같이 asymmetric() 함수를 사용해 방향성을 지정할 수 있습니다.

extension AnyTransition {
    static var moveAndFade: AnyTransition {
        .asymmetric(
            insertion: .move(edge: .leading).combined(with: .opacity),
            removal: .scale.combined(with: .opacity)
        )
    }
}

 

정리

간단합니다. 아래 내용만 알면 됩니다.

 

1. rotationEffect()은 회전 효과 함수

2. scaleEffect() 사이즈 효과 함수

3. animation() 애니메이션 처리 함수로 위 두 개 함수와 같이 사용하면 조금 더 풍성한 애니메이션 효과를 연출할 수 있음.

4. 애니메이션의 유지 시간 등 다양한 효과를 추가하기 위해서는 withAnimation() 함수를 사용해야 함.

5. Animation은 아래와 같은 다양한 애니메이션 효과를 기본 지원함.

    - easeInOut

    - easeIn

    - easeOut

    - linear

    - timingCurve

    - 추가로 speed, delay, repeat 등을 지원함.

6. 필요하다면 extension을 사용해 Animation 뿐만 아니라 Transition 효과도 좀 더 풍성하게 사용할 있음.

Comments