기어가더라도 제대로

[SwiftUI-기초] Animation 기본 본문

SwiftUI - 기초

[SwiftUI-기초] Animation 기본

Damagucci-juice 2022. 10. 28. 09:53

withAnimation { } 도 물론 있지만, 숨겨져 있는 Animation modifier 들을 알아보자.

Button에 붙이는 애니메이션

  • 빨간색 원을 만들고 버튼을 누를 때마다 크기가 늘어나도록 설정을 해봄
  • . scaleEffect() : 이 모디파이어를 사용해서 버튼의 크기를 조절할 것임
struct ContentView: View {
    @State private var animationAmount = 1.0

    var body: some View {
        Button("Tap Me") {
            animationAmount += 1
        }
        .padding(50)
        .background(.red)
        .foregroundColor(.white)
        .clipShape(Circle())
        .scaleEffect(animationAmount)
    }
}
  • 0에서 1 사이의 값을 전달할 수 있음
  • animationAmount의 값이 0 이면 버튼이 사라지고 1이면 버튼이 원래 크기
  • 여기서 버튼을 누를 때 animationAmount에 1의 값을 더하면 두 배의 크기로 늘어남

animation

.animation(.default, value: animationAmount)
  • 스케일이 커지는 상황을 동적으로 표현함
  • .default 대신 사용할 수 있는 값들이 있는데 이따 알아보도록 하겠음

blur

  • 흐릿하게 하는 효과를 줍니다.
  • 버튼을 한번 눌렀을 때, 흐릿해진 모습
Button("Tap Me") {
    animationAmount += 1
}
.padding(50)
.background(.red)
.foregroundColor(.white)
.clipShape(Circle())
.scaleEffect(animationAmount)
.blur(radius: (animationAmount - 1) * 3)
.animation(.default, value: animationAmount)
  • .animation modifier 앞에 동적으로 변화할 속성들이 먼저 선언되어 있어야 변화가 애니메이션이 됨
  • .blur 에 radius 같은 경우, 기본 값인 1인 경우엔, 가장 0이니까 가장 선명하고 한번 탭할 때마다 3배씩 흐릿해짐

Animation Variation

  • .default 대신 사용할 수 있는 값들이 있음
  • .easeOut: 빠르게 늘어나고 끝부분에 올쯤 속력이 줄음
  • .interpolatingSpring: 용수철 효과
.animation(.interpolatingSpring(stiffness: 50, damping: 1), value: animationAmount)
  • stiffness: 초기에 스프링이 응축된 정도, 즉 튕기는 속도를 조절
  • damping: 값이 낮을수록 더 오래 댕겅 거림
  • .default 와 같은 요소도 animation 이기 때문에 자체에 지속시간을 달 수 있음
.animation(.easeInOut(duration: 2), value: animationAmount)
  • 딜레이 : 1초 후에 2초 동안 애니메이션 실시
.animation(
    .easeInOut(duration: 2)
        .delay(1),
    value: animationAmount
)
  • 되돌아오기(?) Auto reverse
.animation(
    .easeInOut(duration: 1)
        .repeatCount(3, autoreverses: true),
    value: animationAmount
)
  • 원형으로 늘어났다가 줄어들었다가 다시 늘어난다.
  • 3에서 2로 변경되면 늘어났다가 줄어들었다가 까지는 애니메이션이 적용되고, 변화 후의 크기만큼은 애니메이션이 적용 안된 채로 늘어남
  • 계속 반복
    • 아예 계속 반복시킴
.animation(
    .easeInOut(duration: 1)
        .repeatForever(autoreverses: true),
    value: animationAmount
)

Animation variation 2

  • 파문을 그리면서 파동형으로 번져나가는 애니메이션을 해볼 것임
  • 기존에 애니메이션을 지우고 overlay라는 것을 사용
.overlay(
    Circle()
        .stroke(.red)
        .scaleEffect(animationAmount)
        .opacity(2 - animationAmount)
        .animation(
            .easeOut(duration: 1)
                .repeatForever(autoreverses: false),
            value: animationAmount
        )
)
  • 현행은 버튼을 한번 눌러줘야 파동이 늘어나는데, .onAppear() 되자마자 바로 파동이 보이도록 하고 싶다면..
Button("Tap Me") {
    animationAmount += 1
}
.padding(50)
.background(.red)
.foregroundColor(.white)
.clipShape(Circle())
.overlay(
    Circle()
        .stroke(.red)
        .scaleEffect(animationAmount)
        .opacity(2 - animationAmount)
        .animation(
            .easeOut(duration: 1)
                .repeatForever(autoreverses: false),
            value: animationAmount
        )
)
.onAppear {
    animationAmount = 2
}

pulsegif

View 의 변화를 암묵적으로 애니메이이션 주기

  • .animation() 같은 바인딩은 거의 모든 SwiftUI 오브젝트에 적용할 수 있음
  • @State 의 변화를 감지해서 기존의 뷰에 변화를 적용할 상태를 감지하고, 변화 후에 상태를 반영하기 때문임
  • 말이 어려우니 직접 보도록하자.
  • 전에 예제에서 stepper 를 달건데, 여기만 애니메이션을 주고, button 에는 안주고 싶음.
struct ContentView: View {
    @State private var animationAmount = 1.0

    var body: some View {
        print(animationAmount)

        return VStack {
            Stepper("Scale amount", value: $animationAmount.animation(), in: 1...10)

            Spacer()

            Button("Tap Me") {
                animationAmount += 1
            }
            .padding(40)
            .background(.red)
            .foregroundColor(.white)
            .clipShape(Circle())
            .scaleEffect(animationAmount)
        }
    }
}
  • 문법이 조금 변경되었다.
  • Print 는 뷰와 관련이 없는 코드여서 Body 가 오인을 할 수 있으니,
    VStack 부분이 View 에 관계된 부분이라고 명시적으로 return 으로 알려주었음
  • 이 코드의 요지는 stepper 의 value 값이 변화하는 것을 animation 을 줄 수 있다는 것임

stepperAnimation

  • binding 에 animation 을 걸어주었는데, 딜레이나, 마찬가지로 걸어줄 수 있다.
Stepper("Scale amount", value: $animationAmount.animation(
    .easeInOut(duration: 1)
        .repeatCount(3, autoreverses: true)
), in: 1...10)

명시적으로 애니메이션 주기

다시 말하지만, SwiftUI에서 애니메이션 효과를 주는 방식에는 3가지가 있다.

  • 암시적인 방법
    • View에 .animation() modifier 를 달기
    • Binding에 .animation() modifier 사용하기
  • 명시적인 방법
    • withAnimation { } 에 변화를 주기

또한, binding 과 withAnimation 이 애니메이션을 주는 원리에 대해서 간단히 짚고 넘어가자면,

두 방법은 앱이 state 의 변화를 감지하면, 변화전의 값과 변화후의 값을 계산해 내고 해당 변화만큼 애니메이션을 줍니다.

즉, @State 변수에 값만 변화 시키면, 세부적으로 어떤 방식으로 애니메이션을 그려라 라는 명령을 내리지 않고, 자동적으로 그려준다는 것이지요.

명시적인 방법에 대해서 알아봅시다.

struct ContentView: View {
	@State private var animationAmount = 0.0
    var body: some View {
        Button("Tap Me") {
            withAnimation {
                animationAmount += 360
            }
        }
        .padding(50)
        .background(.red)
        .foregroundColor(.white)
        .clipShape(Circle())
        .rotation3DEffect(.degrees(animationAmount), axis: (x: 1, y: 0, z: 0))
    }
}
  • 이번 실습을 도와줄 메서드는 .rotation3DEffect() 라는 친구입니다.
  • 이 친구는 변화를 감지할 state를 하나 파라미터로 받습니다.
  • 또 어떤 축을 기준으로 변화를 할지도 결정하는 axis라는 파라미터를 받습니다. 못을 박을 때, 그 축을 기준으로 회전하는 모습을 상상해보세요.
    • x: 수평선을 기준으로 앞뒤로 회전합니다.
    • y: 수직선을 기준으로 좌우로 회전합니다.
    • z: 시계방향으로 왼쪽으로 오른쪽으로 스핀합니다.

 

withAnimation(.interpolatingSpring(stiffness: 5, damping: 1)) {
    animationAmount += 360
}
  • 애니메이션이다 보니 어떤 식으로 표현할지도 설정할 수 있습니다. 
Comments