[SwiftUI] 애니매이션 갖고 놀기

ciracino88·2024년 6월 13일
0

SwiftUI 기능 구현

목록 보기
3/3

바탕화면에 있는 앱을 꾹 누르고 있으면 앱이 덜덜 떨리면서 수정 상태에 들어가고 드래그가 가능해진다. 이른바 진동 애니매이션이다. 이를 구현해보고자 한다.

  1. 꾹 누른다.
  2. 달달 떨리는 상태로 바뀐다.
  3. Draggable 한 상태로 바뀐다.
  4. 제스쳐를 취해서 작업을 처리.

결론부터 말하면, 진동하면서 드래그로 이동하는 행위는 구현에 실패했다.
삽질 이후의 결과물은 '크기 조작 + 드래그 기능' 부터 진행된다.


진동 애니매이션 구현

진동 애니매이션 자체를 구현하는 것은 생각보다 간단하다. animation 기능을 이용해서 움직임을 부여하면 된다. 진동 움직임은 단순하게 좌우로만 흔들리는 정도로만 구현해보자.


진동 여부 체크

ShakingView 라는 이름으로 파일을 하나 만들어준다.
진동 상태에 돌입했는지를 판단하기 위한 변수를 하나 만들어준다.

struct ShakingView: View {
    @State private var is_shaking = false
    
    var body: some View {
        Text("Shake Me")
        	.padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
    }
}

움직임 부여

이제 이놈이 움직일 수 있도록 만들어야 한다.
실제 위치가 움직이는 것이 아니라, 움직이는 것처럼 모습만 보여주는 것으로 충분하므로 offset 을 이용한다. offset 은 대상의 실제 위치는 그대로이지만 그림자가 움직이는 것이라고 이해하면 편하다.

좌우로만 흔들거니까 x값만 변동을 주면 된다.
offset은 진동 여부에 따라 위치를 움직일지 말지를 결정해주면 된다.

다음으로 animation 부분이다.

  • linear 를 주면 부드러운 움직임을 줄 수 있다.
  • duration 은 몇 초마다 움직이는 행동을 할 것인지를 결정한다. 여기서는 진동 속도라고 생각하면 된다.
  • autoreverses 는 애니매이션에 루프를 걸지를 결정한다. 이걸 키면 애니매이션을 무한히 반복하고, 끄면 1번만 실행 후 종료한다.
struct ShakingView: View {
    @State private var is_shaking = false
    
    var body: some View {
        Text("Shake Me")
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
            .offset(x: is_shaking ? -10 : 0)
            .animation(
                Animation.linear(duration: 0.1)
                    .repeatForever(autoreverses: true)
            )
            .onTapGesture {
                self.is_shaking.toggle()
                
            }
    }
}

LongPressGesture

이제 위에서 만든 놈을 1초 동안 누르고 있으면 진동 상태가 되도록 만들어보자. 이는 LongPressGesture 를 이용해서 구현할 수 있다.

LongPressGesture 에 대해 흔히 하는 오해가 있는데, 이 제스쳐는 드래그를 통해 움직이는데 사용되는 기능이 아니다. LongPressGesture 는 minimumDuration 라는 변수를 받는다. 이는 사용자가 얼마나 누르고 있어야 기능을 발동할지를 결정한다. minimumDuration 에 다다르면 제스쳐는 딱 한 번만 실행된다. 연속적으로 실행되는 것이 아니다!

따라서 해당 기능은 진동 애니매이션을 키는 용도로만 사용한다.

struct ShakingView: View {
    @State private var is_shaking = false
    
    var body: some View {
        let longPress = LongPressGesture(minimumDuration: 1.0)
            .onEnded { _ in
                withAnimation(Animation
                    .linear(duration: 0.1)
                    .repeatForever(autoreverses: true)) {
                        self.is_shaking = true
                }
            }
        Text("Shake Me")
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
            .offset(x: is_shaking ? -10 : 0)
            .gesture(longPress)
    }
}

계획 수정

여러 가지 노력을 해봤지만 오프셋을 동시에 조작하는 행위는 불가능했다.

  1. 드래그 제스쳐와 롱프레스 제스쳐를 두 개 다 .gesture 로 걸어본다.
  2. 롱프레스는 .gesture 로 걸고, 드래그는 .simultaneousGesture 로 걸어본다.
  3. 진동 움직임은 offset 으로, 드래그 움직임은 position 으로 설정
  4. .sequenced 를 이용해서 먼저 드래그를 받고, 롱프레스를 받아보기
  5. 롱프레스에서는 x만 움직이고, 드래그 상태일 때는 x와 y값 둘 다 조정해보기.

위에 실험 결과 모두, 드래그 시도 시, 진동이 멈추는 이슈가 발생했다.

결론은 오프셋 조작 애니매이션은 하나만 걸 수 있다. 정확히 말하자면, 각각의 제스쳐는 다른 역할을 심어줘야한다. 서로 다른 제스쳐가 같은 종류의 애니매이션을 수행하게 하는 일은 어렵고 비효율적이다.


크기 조작 + 드래그 기능

위에서 삽질을 하면서 알게 된 점은 제스쳐를 동시에 처리할 수 있는 기능이 존재한다는 것이다. 다른 종류의 작업만 걸어준다면 제스쳐는 동시 수행이 가능하다. 따라서, 이번에는 꾹 눌렀을 때 객체의 크기가 커지고, 드래그로 움직이는 기능을 만들어보겠다.

먼저, 꾹 눌렀을 때와, 드래그 할 때의 상태를 확인할 변수를 설정한다.

struct ShakingView: View {
    @State var longPressing = false
    @State var position: CGSize = .zero
    
    var body: some View {
        
        Text("Shake Me")
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
    }
}

다음으로 꾹 누를 때와, 드래그 할 때 처리할 로직을 작성한다.

let longPress = LongPressGesture()
	.onChanged { _ in
		withAnimation {
			self.longPressing = true
		}
	}
	.onEnded { _ in
		withAnimation {
        	self.longPressing = false
		}
	}
        
	let drag = DragGesture()
        .onChanged { value in
            self.position = value.translation
        }
        .onEnded { _ in
            withAnimation {
                self.longPressing = false
                self.position = .zero
			}
		}

두 개의 애니매이션을 연속적으로 처리하도록 합쳐준다.
simultaneously 를 이용하면 동시에 처리가 가능하다!

let continueGesture = longPress.simultaneously(with: drag)

마지막으로 애니매이션 변수에 따른 크기 변화 및 위치 변화면 객체에 적용해주면 끝이다.

struct ShakingView: View {
    @State var longPressing = false
    @State var position: CGSize = .zero
    
    var body: some View {
        let longPress = LongPressGesture()
            .onChanged { _ in
                withAnimation {
                    self.longPressing = true
                }
            }
            .onEnded { _ in
                withAnimation {
                    self.longPressing = false
                }
            }
        
        let drag = DragGesture()
            .onChanged { value in
                self.position = value.translation
            }
            .onEnded { _ in
                withAnimation {
                    self.longPressing = false
                    self.position = .zero
                }
            }
        
        let continueGesture = longPress.simultaneously(with: drag)
        
        Text("Shake Me")
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
            .offset(position)
            .scaleEffect(longPressing ? 1.2 : 1.0)
            .gesture(continueGesture)
            
    }
}

profile
깔끔한 메모장에 NLP 한스푼이 곁들어진 앱을 만드는게 목표

0개의 댓글

관련 채용 정보