-
[Swift] 애니메이션 정지 재생 CGAffineTransform개발/Swift 2025. 4. 11. 17:11
애니메이션 정지 재생
탭 이벤트를 적용할떄 탭을 눌렀다가 떼었을때 처리를 위해서는 Tap Gesture보다는
LongPressGesture를 활용하는게 더 간편한다
let tapGesture = UILongPressGestureRecognizer() tapGesture.minimumPressDuration = 0 optionView.addGestureRecognizer(tapGesture)
minimumPressDuration 을 0으로 지정하면
누르자마자 gesutre.state 가 began 되므로
바로 터치다운 이벤트를 시작할수있다
switch gestureState { case .began: optionView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) case .ended, .cancelled: optionView.transform = .identity default: return
위는 view의 scale을 0.9로 줄이는 동작이다.
그리고 .identity를 적용하면 다시 원래대로 돌아온다
만약 진행중인 애니메이션이 있다면 애니메이션을 멈첬다 재생할수있다.
해당 view의 layer 를 가지고 적용시켜준다.
speed를 0으로 지정하여 애니메이션이 멈추게하고 (1이면 원래속도)
timeOffset 을 지금 시간으로 지정해준다.
private func pauseAnimation(layer: CALayer) { let pausedTime = layer.convertTime(CACurrentMediaTime(), from: nil) layer.speed = 0 layer.timeOffset = pausedTime } private func resumeAnimation(layer: CALayer) { let pausedTime: CFTimeInterval = layer.timeOffset layer.speed = 1.0 layer.timeOffset = 0.0 layer.beginTime = 0.0 let timeSincePause: CFTimeInterval = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime layer.beginTime = timeSincePause }
beginTime 을 레이어에 저장해놨던 시간으로 바꾸고 스피드를 다시 돌려놔서 재생할수 있다.
다른 제스쳐와 충돌
optionView.rx.controlEvent(.touchUpInside) .do(onNext: { [weak self] in self?.delegate?.sendBenefitMissionSucceed(type: .card) }) .map { Reactor.Action.selectButton(id) } .bind(to: reactor.action) .disposed(by: disposeBag)
그런데 이미 다른곳에서 제스처를 사용중이었는데
LongPress의 minimum duration이 0이라 바로 동작하므로
기존 이벤트 동작이 이루어지지 않았다
minimum duration 을 올려서 해결할수 있지만 그러면 늦게 반응이 와서 좋지 않았다
위같은 경우 같은 Gesture에서 터치업 이벤트를 처리해야했다
private func handleTapOptionView(gesture: UILongPressGestureRecognizer, optionView: UIView) { if case .ended = gesture.state, let id = (optionView as? BenefitQuizOptionView)?.id, let gestureView = gesture.view { let location = gesture.location(in: gestureView) if gestureView.bounds.contains(location) { // 위치 테스팅 delegate?.sendBenefitMissionSucceed(type: .card) reactor?.action.onNext(.selectButton(id)) } }
정상적으로 터치 업 할 경우에만 ended 가 될줄 알았는데 그게 아니라 누르고 바깥으로 빼도 ended가 호출 되었다
그래서 원하는 방향으로 동작하지 않았다.
그러므로 gesture.view의 위치 테스팅을 통해서 end 상태에서 위치가 해당 뷰 안에있는지 체크해줘야
올바르게 touch up 된거라 볼수있다.
기존 애니메이션 붕괴
해당 LongPress 이벤트가 있기전에 해당 뷰에 다른 애니메이션이 적용되어있었다
UIView.animateKeyframes(withDuration: withDuration, delay: 0, options: keyframeAnimationOptions) { UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: Metric.Animation.labelDuration / withDuration) { self.labelContainer.transform = CGAffineTransform(translationX: 0, y: Metric.Animation.labelContainerTranslationY) self.labelContainer.layer.opacity = 1 } optionViews.enumerated().forEach { index, view in let startTime = Metric.Animation.optionStartTime + eachOptionDelay * Double(index) UIView.addKeyframe(withRelativeStartTime: startTime / withDuration, relativeDuration: Metric.Animation.optionDuration / withDuration) { view.transform = CGAffineTransform(translationX: 0, y: Metric.Animation.optionTranslationY) view.layer.opacity = 1 } } let labelStartTime = withDuration - Metric.Animation.participantLabelDuration UIView.addKeyframe(withRelativeStartTime: labelStartTime / withDuration, relativeDuration: Metric.Animation.participantLabelDuration) { self.participantsLabel.layer.opacity = 1 } } completion: { _ in if !isMultipleOptionAnimation { self.startOXRepeatAnimation(optionViews: optionViews) } self.addEventToOptionView(optionViews: optionViews) // LongPress 이벤트 주는곳 }
복잡해 보이지만 결국 CGAffineTransform 을 사용하여 y 위치를 위로 올린것이다.
근데 아까 pauseAnimation 함수에서
switch gestureState { case .began: optionView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) case .ended, .cancelled: optionView.transform = .identity default: return
view의 transform 자체를 바꿔버렸다. 그러므로 기존 transform 이 사라지는 것이다
기존 tramsform을 유지하고 바꿀거만 (사이즈만) 바꿔줘야한다.
switch gestureState { case .began: optionView.transform = optionView.transform.scaledBy(x: Metric.Animation.touchScale, y: Metric.Animation.touchScale) case .ended, .cancelled: optionView.transform = optionView.transform.scaledBy(x: 1.0 / Metric.Animation.touchScale, y: 1.0 / Metric.Animation.touchScale) default: return
scaledBy 만 수정하여 해결
'개발 > Swift' 카테고리의 다른 글
Observable → Async / Await 으로 변환하여 API 처리하기 (AsyncThrowingStream, withCheckedThrowingContinuation) (0) 2025.04.16 SwiftUI TextEditor에 키보드 toolbar가 노출되지 않는 오류 (0) 2025.04.16 UIKit SwiftUI에서 자동 스크롤 (0) 2025.04.08 WKWebview 에서 post 요청하는방법 (0) 2025.04.08 [SwiftUI] NavigationLink pop push 깜빡이는 문제 (0) 2025.04.08