기어가더라도 제대로

[SwiftUI-기초] Animation의 View적인 특성(+ Drag, transition) 본문

SwiftUI - 기초

[SwiftUI-기초] Animation의 View적인 특성(+ Drag, transition)

Damagucci-juice 2022. 10. 30. 09:31
  • animation도 background, padding, 과 마찬가지로 some View 를 반환한다.
  • 여기서 도출되는 결론이 두가지 있다.
  1. 애니메이션을 적용하는 순서가 중요하다.
  2. 여러개의 애니메이션을 중첩해서 사용할 수 있다.

위의 두 가지 결론을 쫒아가보는 포스팅이 될 것이다.

View 적인 특성에 대해서 알고 싶으시다면 다음 포스팅을 참조하시라.

2022.10.14 - [Swift - 기초] - [SwiftUI-기초] Modifier 적용 순서가 중요한 이유

애니메이션의 적용 순서가 중요한 이유

결론부터 말하자면, .animation() modifier 앞에 적용된 modifier들만 애니메이션 처리가 된다.

.animation 뒤로 적용된 modifier의 경우에는 애니메이션 처리가 되지 않는다.

@State private var enabled = false

var body: some View {
    Button("Tap Me") {
        enabled.toggle()
    }
    .frame(width: 200, height: 200)
    .background(enabled ? .blue : .red)
    .animation(.default, value: enabled)
    .foregroundColor(.white)
}
  • 위의 코드는 배경색이 state 의 변화에 따라 파랑색에서 빨간색으로 변화하는 변화 자체가 애니메이션화 된다.
  • 하지만 .animation 과 .background 의 순서가 바뀌면 애니메이션이 적용이 안된채로 바로 변화하게 된다.

애니

  • 배경색 변화가 애니메이션 처리 된 모습

안애니

  • 배경색 변화가 애니메이션 처리 안된 모습

애니메이션의 선언 위에 있는 코드들만이 애니메이션 변화의 효과를 적용받게 된다.

여러 .animation() 의 중첩

Button("Tap Me") {
    enabled.toggle()
}
.frame(width: 200, height: 200)
.background(enabled ? .blue : .red)
.animation(.default, value: enabled)
.foregroundColor(.white)
.clipShape(RoundedRectangle(cornerRadius: enabled ? 60 : 0))
.animation(.interpolatingSpring(stiffness: 10, damping: 1), value: enabled)
  • 애니메이션이 배경색을 적용한 애니메이션 하나와, 도형의 모양에 영향을 주는 애니메이션이 두개가 있다.
  • 다음과 같이 적용이 됩니다.

흐물애니

  • 용수철 효과가 적용된 모습
  • 짤방의 화면 한계 때문에 덜 보이는데, 빨강, 파랑 색 전환도 애니메이션을 먹는 모습입니다. 도형의 모양도 당연히 애니메이션을 받고요.
  • 만약에 색전환은 애니메이션 적용을 안하고, 도형의 모양만 애니메이션 적용을 하고 싶다면 어떻게 해야할까요?
    • 첫번째 선언된 애니메이션을 빼야할까요?
    • 그렇지 않습니다.
    • 위의 애니메이션을 빼면, 최종 애니메이션에 의해서 어차피 적용이 될거에요.
    • 정답은 .default 대신 nil 를 넣는 것입니다.
Button("Tap Me") {
            enabled.toggle()
        }
        .frame(width: 200, height: 200)
        .background(enabled ? .blue : .red)
        .animation(nil, value: enabled)
        .foregroundColor(.white)
        .clipShape(RoundedRectangle(cornerRadius: enabled ? 60 : 0))
        .animation(.interpolatingSpring(stiffness: 10, damping: 1), value: enabled)
  • 이렇게 하면 첫번째 애니메이션 modifier 위로는 애니메이션을 적용하지 않는다라고 선언하는 것과 같습니다.

색상애니

  • 색상이 애니메이션 없이 전환되는 모습
  • 도형 변화는 애니메이션이 적용 되었다.

View 에 드래그 제스쳐 적용하기

view애니

  • 3가지를 다루면 위에 내용을 구현할 수 있습니다.
  • 1. drag 를 할 때 위치를 저장할 State 를 선언한다.
  • 2. .offset() 으로 원래에 얼마만큼 떨어졌는지를 붙여준다. (마침, CGSize 값도 파라미터로 받습니다.)
  • 3. drag 제스쳐를 붙여준다.(이동중, 끝을 알리는 클로저를 선언한다.)

스텝 바이 스텝으로, 사각형 하나를 움직이는 로직을 이해해보죠.

struct ContentView: View {
    @State private var dragAmount = CGSize.zero

    var body: some View {
        LinearGradient(gradient: Gradient(colors: [.yellow, .red]), startPoint: .topLeading, endPoint: .bottomTrailing)
            .frame(width: 300, height: 200)
            .clipShape(RoundedRectangle(cornerRadius: 10))
            .offset(dragAmount)
            .gesture(
                DragGesture()
                    .onChanged { dragAmount = $0.translation }
                    .onEnded { _ in dragAmount = .zero }
            )
            .animation(.spring(), value: dragAmount)


    }
}
  • 300 * 200 정도의 카드를 움직이는 로직입니다.
  • .offset(dragAmount): 원래 위치와 얼마만큼 떨어져있는지를 알려주는 modifier 입니다.
  • .gesture(): 안에 tap 제스쳐도 넣을 수 있고, 이번에는 drag 제스쳐를 넣었습니다.
    • .onChage { } 를 보면, 현재 움직이는 위치를 $0 이라고 한다면, 그것의 위치를 변환해서 dragAmount 에 넣는 것입니다.
    • 이대로면 당겨오겠죠?
    • .onEnded { } 이것은 드래그하는 손가락을 때었을 때 나타나는 모습입니다. 다시 원위치로 돌아가는 모습입니다.
  • .animation() 매끄러운 애니메이션 변화를 주었고요.
    • 근데 한가지 문제가 있습니다. 드래그 시작부터 애니메이션이 걸려서 좀 실제 손가락과 딜레이가 느껴집니다.
    • 움직임 딜레이는 없되, 되돌아가는 모습은 애니메이션을 넣어주는 방식으로 해결 가능하겠네요 .
.gesture(
    DragGesture()
        .onChanged { dragAmount = $0.translation }
        .onEnded { _ in
            withAnimation(.spring()) {
                dragAmount = .zero
            }
        }
)
  • 마지막에 .animation 은 지워야합니다.

드래그 애니

첫 번째 움짤의 코드는 글자의 배열만큼 옮기는 작업이 추가된 것입니다.

전체 코드입니다.

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()
                }
        )
    }
}

뷰 화면 전환 - transition

  • if 문을 통해서 어떤 뷰를 넣을지 말지를 결정한 적이 있다.
  • 어떤 뷰가 생길 때 자신의 화면 전환을 어떻게 담당할지 정하는 방법이 있는데 .trasition() 이라고 한다.
  • 다음은 버튼을 눌렀을 때 빨간색 사각형을 띄우는 코드이다.
struct ContentView: View {
    @State private var isShowingRed = false


    var body: some View {
        VStack {
            Button("Tap Me") {
                isShowingRed.toggle()
            }

            if isShowingRed {
                Rectangle()
                    .fill(.red)
                    .frame(width: 200, height: 200)
            }
        }
    }
}
  • 이 코드를 애니메이션을 넣는 방법은 다음과 같다.
struct ContentView: View {
    @State private var isShowingRed = false


    var body: some View {
        VStack {
            Button("Tap Me") {
                withAnimation {
                    isShowingRed.toggle()
                }
            }

            if isShowingRed {
                Rectangle()
                    .fill(.red)
                    .frame(width: 200, height: 200)
                    .transition(.scale)
            }
        }
    }
}

스케일 애니

  • 또한 .transition 에 insert 하는 화면하고 removal 하는 화면을 나눠서 편집할 수가 잇는데, 다음과 같다.
.transition(.asymmetric(insertion: .scale, removal: .opacity))

스케일2

  • 스케일을 키우면서 등장하고, 흐리게 페이드 아웃하며 사라진다.
Comments