[Swift] 애니메이션 정지 재생 CGAffineTransform
애니메이션 정지 재생
탭 이벤트를 적용할떄 탭을 눌렀다가 떼었을때 처리를 위해서는 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 만 수정하여 해결