첫번째로, 애니메이션 효과를 줄 xib view를 생성하고 CardViewController에 IBOutlet으로 연결해준다.
그 다음으로 실행에 필요한 것들을 설정해준다.
enum CardState {
case expanded
case collapsed
} // card view의 상태
// xib 참조
var cardViewController: CardViewController!
// 애니메이션 효과
var visualEffectView: UIVisualEffectView!
let cardHeight: CGFloat = 600
let cardHadleAreaHeight: CGFloat = 65
var cardVisible = false
var nextState: CardState {
return cardVisible ? .collapsed : .expanded
}
// 실행할 모든 애니메이션을 배열로 담아놓는다.
var runningAnimations = [UIViewPropertyAnimator]()
var animationProgressWhenInterrupted: CGFloat = 0
그 다음, card view를 세팅하는 함수를 만들어준다.
func setupCard() {
// Add Visual Effects View
visualEffectView = UIVisualEffectView()
visualEffectView.frame = self.view.frame
self.view.addSubview(visualEffectView)
// Add CardViewController xib to the bottom of the screen
cardViewController = CardViewController(nibName: "CardViewController", bundle: nil)
self.addChild(cardViewController)
self.view.addSubview(cardViewController.view)
cardViewController.view.frame = CGRect(x: 0, y: self.view.frame.height - cardHadleAreaHeight, width: self.view.bounds.width, height: cardHeight)
// clipping bounds so that the corners can be rounded
cardViewController.view.clipsToBounds = true
// Add tap and pan recognizers
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleCardTap(recognizer:)))
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(ViewController.handleCardPan(recognizer:)))
cardViewController.handleArea.addGestureRecognizer(tapGestureRecognizer)
cardViewController.handleArea.addGestureRecognizer(panGestureRecognizer)
}
@objc
func handleCardTap(recognizer: UITapGestureRecognizer) {
}
@objc
func handleCardPan(recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
// 클릭
case .began:
startInteractiveTransition(state: nextState, duration: 0.9)
// 변화가 있을 때
case .changed:
let translation = recognizer.translation(in: self.cardViewController.handleArea)
var fractionComplete = translation.y / cardHeight
fractionComplete = cardVisible ? fractionComplete : -fractionComplete
updateInteractiveTransition(fractionCompleted: fractionComplete)
// 움직임이 끝났을 때
case .ended:
continueInteractiveTransition()
default:
break
}
}
애니메이션이 진행될 때의 transition을 모두 설정해준다.
func animationTransitionIfNeeded(state: CardState, duration: TimeInterval) {
if runningAnimations.isEmpty {
let frameAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 1) {
switch state {
// 카드를 위로 올릴 때
case .expanded:
self.cardViewController.view.frame.origin.y = self.view.frame.height - self.cardHeight
// 카드를 아래로 내릴 때
case .collapsed:
self.cardViewController.view.frame.origin.y = self.view.frame.height - self.cardHadleAreaHeight
}
}
frameAnimator.addCompletion { _ in
self.cardVisible = !self.cardVisible
self.runningAnimations.removeAll()
}
frameAnimator.startAnimation()
runningAnimations.append(frameAnimator)
// 카드 뷰에 corner radius 효과
let cornerRadiusAnimator = UIViewPropertyAnimator(duration: duration, curve: .linear) {
switch state {
// 카드를 위로 올릴 때
case .expanded:
self.cardViewController.view.layer.cornerRadius = 12
// 카드를 아래로 내릴 때
case .collapsed:
self.cardViewController.view.layer.cornerRadius = 0
}
}
cornerRadiusAnimator.startAnimation()
runningAnimations.append(cornerRadiusAnimator)
// 뒷 배경 블러 효과
let blurAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 1) {
switch state {
// 카드를 위로 올릴 때
case .expanded:
self.visualEffectView.effect = UIBlurEffect(style: .dark)
// 카드를 아래로 내릴 때
case .collapsed:
self.visualEffectView.effect = nil
}
}
blurAnimator.startAnimation()
runningAnimations.append(blurAnimator)
}
}
func startInteractiveTransition(state: CardState, duration: TimeInterval) {
if runningAnimations.isEmpty {
// run animations
animationTransitionIfNeeded(state: state, duration: duration)
}
for animator in runningAnimations {
animator.pauseAnimation()
// view를 올릴 때 흐리게 처리하는 정도 : fractionComplete
animationProgressWhenInterrupted = animator.fractionComplete
}
}
func updateInteractiveTransition(fractionCompleted: CGFloat) {
for animator in runningAnimations {
animator.fractionComplete = fractionCompleted + animationProgressWhenInterrupted
}
}
func continueInteractiveTransition() {
for animator in runningAnimations {
animator.continueAnimation(withTimingParameters: nil, durationFactor: 0)
}
}
실행 결과