iOS 기본 (SwiftUI)

[iOS - SwiftUI] Animatable, EmptyAnimatableData, AnimatableData 사용 방법

jake-kim 2022. 10. 11. 23:05

목차) SwiftUI의 기본 - 목차 링크

Animatable

  • 프로토콜이며 animatableData 프로퍼티를 가지고 있는 프로토콜
    • 특정 값을 뷰에 주입하면, animatable 프로토콜을 통해 미리 정해둔 값을 이용하여 내부적으로 계산하여 미리 애니메이션에 사용할 데이터를 정의해둔다는 의미
/// A type that describes how to animate a property of a view.
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol Animatable {

    /// The type defining the data to animate.
    associatedtype AnimatableData : VectorArithmetic

    /// The data to animate.
    var animatableData: Self.AnimatableData { get set }
}
  • extension으로 animatableData는 EmptyAnimatableData 타입
    • EmptyAnimatableData 타입이란, animatableData 프로퍼티에 어떤 애니메이션도 가지고 있지 않는 타입
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension Animatable where Self.AnimatableData == EmptyAnimatableData {

    /// The data to animate.
    public var animatableData: EmptyAnimatableData
}

기본 지식 - custom Shape 사용 방법

  • Shape를 준수하고, path(in:) 메소드를 구현
struct MyShape: Shape {
  func path(in rect: CGRect) -> Path {
    Path {
      $0.move(to: CGPoint(x: rect.midX, y: rect.minY))
      $0.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
      $0.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
      $0.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
    }
  }
}

사용)

struct ContentView: View {
  var body: some View {
    VStack {
      MyShape()
        .fill(.blue)
        .frame(width: 150, height: 150)
    }
  }
}

Animatable 사용 방법

  • Shape라는 프로토콜은 Animatable을 준수하고 있어서, 커스텀 Shape를 만들때  animatableData를 정의하여 사용
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol Shape : Animatable, View {
    func path(in rect: CGRect) -> Path

    @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
    static var role: ShapeRole { get }
}
  • 애니메이션과 함께 offsetY라는 것을 단순히 주입하면, 내부적으로 정의한 Animatable로 인해 애니메이션에 사용될 데이터가 변경되도록 구현
struct MyShape2: Shape {
  var offsetY: CGFloat
  
  var animatableData: CGFloat {
    get { offsetY }
    set { offsetY = newValue }
  }
  
  func path(in rect: CGRect) -> Path {
    Path {
      $0.move(to: CGPoint(x: rect.midX, y: rect.minY))
      $0.addLine(to: CGPoint(x: rect.minX, y: offsetY))
      $0.addLine(to: CGPoint(x: rect.maxX, y: offsetY))
      $0.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
    }
  }
}

 

사용하는쪽)

struct ContentView: View {
  @State var offset = 150.0
  
  var body: some View {
    MyShape2(offsetY: offset)
      .fill(.blue)
      .onTapGesture {
        withAnimation(.easeIn) {
          offset = Double(Array(50...250).randomElement()!)
        }
      }
  }
}

만약 animatableData를 구현 안하는 경우?

  • 아래처럼 animatableData부분을 주석처리하면, 사용할때 애니메이션 없이 값만 변경
struct MyShape2: Shape {
  var offsetY: CGFloat
  
//  var animatableData: CGFloat {
//    get { offsetY }
//    set { offsetY = newValue }
//  }
  
  func path(in rect: CGRect) -> Path {
    Path {
      $0.move(to: CGPoint(x: rect.midX, y: rect.minY))
      $0.addLine(to: CGPoint(x: rect.minX, y: offsetY))
      $0.addLine(to: CGPoint(x: rect.maxX, y: offsetY))
      $0.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
    }
  }
}

animatableData 정의 안하면 애니메이션 적용이 안됨

* 전체 코드: https://github.com/JK0369/ExAnimatable

 

* 참고

https://developer.apple.com/documentation/swiftui/emptyanimatabledata