XLOG

[SwiftUI] Camera Shutter Button Animation 본문

Swift/SwiftUI

[SwiftUI] Camera Shutter Button Animation

X_PROFIT 2023. 9. 18. 22:29

저번 Animation 공부한 내용을 바탕으로 Camera Shutter 를 구현해 보았다.

우선 카메라 셔터 부터 시작을 해보자.

struct ContentView: View {
	@State var buttonTapped: Bool = false
    
    var body: some View {
    	ZStack {
        	Color.black
            ZStack {
            	// 외각 테두리
            	Circle()
                	.stroke(lineWidth: 4)
                    .foregroundColor(.white)
                // 내부 원
                Circle()
                    .foregroundColor(buttonColor)
                    .frame(width: buttonTapped ? 56 : 60, height: buttonTapped ? 56 : 60)
            }
            .frame(width: 72, height: 72)
            .onTapGesture {
            	withAnimation(.easeInOut(duration: 0.3)){
                    buttonTapped.toggle()
                }
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                    withAnimation(.easeInOut(duration: 0.3)){
                        buttonTapped.toggle()
                    }
                }
            }
        }
    }
}

UIKit 이 었다면 UIView.animation 의 CompletionHandler 를 통해 다시 되돌아가도록 구현을 했겠지만, SwiftUI 의 withAnimation 에는 completion 이 없는 것 같아 DispatchQueue.main.asyncAfter 를 이용하여 구현하였다.

사실 여기까지는 간단하다.

하지만 조금 고생한 부분은 영상촬영 시작에 대한 문제였다. UIKit 에선 LongPressGestureRecognizer 를 사용하면 제스쳐의 시작과 끝을 알 수 있었다. 하지만 SwiftUI 의 LongPressGesture의 경우 minimumDuration 을 넘기게 되면 gesture의 onEnded가 호출이 되었다. 그래서 여기서 편법을 사용하였다. 그건 바로 DragGesture이다.

DragGesture 는 Gesture 인식이 시작될 때 onChaged 가 호출이 된다. 하지만 주의할 점은 손을 움직일 때마다 onChanged 가 호출이 되기 때문에 그 동작을 막아줘야 한다.

struct ContentView: View {
    
    @State var buttonTapped: Bool = false
    @State var buttonColor: Color = .white
    @State var scale: CGFloat = 1
    
    var body: some View {
        ZStack{
            Color.black
            ZStack {
                Circle()
                    .stroke(lineWidth: 4)
                    .foregroundColor(.white)
                Circle()
                    .foregroundColor(buttonColor)
                    .frame(width: buttonTapped ? 56 : 60, height: buttonTapped ? 56 : 60)
                    .scaleEffect(scale)
            }
            .frame(width: 72, height: 72)
            .onTapGesture {
                ''''''
            }
            .gesture(
                DragGesture(minimumDistance: 0)
                    .onChanged({ value in
                    //살짝 확대될 때의 Scale 값
                        let tempScale: CGFloat = 75 / 60
                        // 애니메이션이 시작하고 진행중인 경우 scale 이 1 인 상황은 처음밖에 없다
                        // 그렇기에 아무리 제스쳐를 움직여도 한번만 애니메이션이 실행되게 된다.
                        if scale == 1 {
                            withAnimation(.easeInOut(duration: 0.3)) {
                                buttonTapped.toggle()
                                buttonColor = .red
                                scale = tempScale
                            }
                        }
                        // withAnimation으로 scale을 tempScale을 바꿔줬을때 duration 뒤에 scale 이 바뀐다는 의미가 아니다.
                        // duraction 을 주더라도 scale 은 즉시 변경이므로 작아지는 애니메이션 또한 한번만 실행
                        if scale == tempScale {
                        	DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                                withAnimation(.easeInOut(duration: 0.3)) {
                                    scale = 50 / 60
                                }
                            }
                        }
                    })
                    .onEnded({ value in
                        withAnimation(.spring()) {
                            withAnimation(.spring(response: 0.2)) {
                                buttonColor = .white
                                scale = 1
                            }
                        }
                    })
            )
        }
    }
}

근데 만들고 나서 기본 카메라 어플을 보니....한번 작아졌다 확대되고 다시 작아진다.....

이걸 구현하기 위해선 UIKit의 TouchesBegan, TapGesutre, LongPressGesutre 를 사용하면 쉽게 될 것으로 보인다.

하지만....SwiftUI 는 바로 떠오르지 않는다....아직 Animation에 대한 공부가 더 필요할 것 같다.

내일이면 새로운 Swift 버전이 풀린다. 그렇게 되면 keyframe을 사용할 수 있으니 DispatchQueue 같은 GCD 를 사용할 필요도 없어질 것 같은데.... 우선 다음에 더 가다듬어 업로드를 해봐야 겠다.


전체코드

Github: https://github.com/profit0124/SwiftUICameraShutterAnimation