iyOmSd/Title: Swift

[Swift] - 화면전환 애니메이션 커스텀: UIViewControllerAnimatedTransitioning

냄수 2020. 4. 25. 01:11
반응형

ViewController간 이동할 때 화려한 애니메이션을 접한적이 있지않나요??

이러한 애니메이션을 주면 앱이 이뻐보이기도 하구요..!

 

 

그래서 해보려고 도전했어요

 

 

먼저 쓰이는 함수를 알아볼게요

 

UIViewControllerAnimatedTransitioning(Protocol)

view controller를 전환할 때 커스텀으로 애니메이션을 주고싶다면 이것을 구현하래요

 

 

UIPercentDrivenInteractiveTransition(Class)

한 컨트롤러와 다른 컨트롤러 간에 상호작용 애니메이션을 구동할 수 있게해주는 역할인것같아요

 

 

UIViewControllerTransitioningDelegate(Protocol)

뷰 컨트롤러 간의 전환을 관리하는 데 사용되는 Delegate같아요

 

이렇게만 보면 잘모르겠어요....

암튼 커스텀 애니메이션을 만들때 이 3가지 함수는 필수요소로 보이네요!!

 

 

 

최종 목표는 앱스토어 애니메이션처럼

누르면 확대되는 동작을 해보려고 해요..

완벽하진않지만 비슷하게나마...

 

 

 

애니메이션동작을 정의할 클래스는 아래와 같아요

class AnimationTransition: UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning {
  	// 애니메이션 동작시간
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {

    }
    // 애니메이션 효과정의
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
       
    }
}

여기에 원하는 동작을 정의하고

UIViewControllerTransitioningDelegate

적용시켜줄거에요

 

extension FirstViewController: UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
    // present될때 실행애니메이션
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return transition
    }
    // dismiss될때 실행애니메이션
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return DisMissAnim()
    }
   
    func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        // Presenting usually doesn't have any interactivity
        return nil
    }
    func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return nil
    }
    
    // UINavigationControllerDelegate push/pop 애니메이션
    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return nil
    }
}

 

animationController(forPresented presented...)

animationController(forDismissed dismissed...)

interactionControllerForPresentation

interactionControllerForDismissal

navigationControlle(..., from fromVC: UIViewController, to toVC: ...)

 

UIViewControllerTransitioningDelegate에는 4개의 함수

UINavigationControllerDelegate 에는 1개의 함수가있어요

 

present될때 실행할 애니메이션 UIViewControllerAnimatedTransitioning 객체를 넣어주면되요

dismiss 도 마찬가지

interactionControllerForPresentation

이 함수는 나타나거나 사라지고 있을때 도중의 작업을 정의하는것 같아요

드래그로 화면을 끌어서 끄는 화면이있다면 도중에 끄고있을때 생기는 애니메이션을

정의하는 거라고 추측하고 있어요

(추후에 더 공부하고 해보려합니다...!)

 

 

 

이제 애니메이션을 만들어볼까요?!

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
	let containerView = transitionContext.containerView
	// 다음 보여질뷰 참조
	guard let toView = transitionContext.view(forKey: .to) else {
		return
	}
	containerView.addSubview(toView)
}

transitionContext.containerView - 뷰를 담을 수 있는 전체 틀이라고 생각하면되요!

 

transitionContext.view를 통해서 보여지거나, 사라질 뷰를 가져올거에요

키값에는 .from, .to 이 잇어요

from - (~로부터) 앞으로 가려질뷰, 사라질 뷰를 가르켜요

to - (~으로) 앞으로 보여질뷰, 나타날 뷰를 가르켜요

 

저 같은경우는

첫번째화면에서(From)

두번째화면으로(To)

Present할거고

그 뷰에 애니메이션을 적용할거니까

To를 참조해야겟죠?

 

틀에 다음뷰를 addSubView를통해서 넣어주면 이제 화면이 나타나죠

 

여기서부터 

다음뷰의 위치, 크기등을 정의해서 애니메이션을 만들어야해요

 

 

왼쪽화면(테이블셀) 을 클릭하면

셀을 기준으로 오른쪽 화면이 커지게 나타날거에요

 

 

간단한 테이블뷰를 만들고...

extension FirstViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 2
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "testCell", for: indexPath)
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        guard let cell = tableView.cellForRow(at: indexPath) else {
            return
        }
        // 좌표계 전환 nil일시 윈도우좌표계
        cellOriginPoint =  cell.superview?.convert(cell.center, to: nil)
        cellOriginFrame =  cell.superview?.convert(cell.frame, to: nil)
        transition = AnimationTransition()
        transition?.setPoint(point: cellOriginPoint)
        transition?.setFrame(frame: cellOriginFrame)
    }
}

cell을 클릭하면 frame을 애니메이션 객체에 넘겨줄거구요

 

 

class AnimationTransition: UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning {
    var originPoint: CGPoint?
    var originFrame: CGRect?
    
    func setPoint(point: CGPoint?) {
        self.originPoint = point
    }
    
    func setFrame(frame: CGRect?) {
        self.originFrame = frame
    }
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 1
    }
    // animation될 뷰지정
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        // 다음 보여질뷰 참조
        guard let toView = transitionContext.view(forKey: .to) else {
            return
        }
        // 보여질뷰의 위치잡기 (Cell의 frame)
        toView.frame = originFrame!

        // MARK: CGAffineTransform을 이용한 효과
        toView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)

        containerView.addSubview(toView)
        // hierarchy on top
        containerView.bringSubviewToFront(toView)
        
        toView.layer.masksToBounds = true
        toView.layer.cornerRadius = 20
        toView.alpha = 0
        // MARK: 애니메이션 적용   
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: .curveEaseOut, animations: {


        // MARK: 원래자리로 되돌리면서 애니메이션 이동효과
        toView.transform = .identity
        toView.alpha = 1
            
        }) { _ in
            toView.translatesAutoresizingMaskIntoConstraints = false
            toView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
            toView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
            toView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
            toView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
            
            UIView.animate(withDuration: 1) {
                containerView.layoutIfNeeded()
            }
        }
        
        transitionContext.completeTransition(true)
    }
}

 

 

위의 코드는

초기 뷰의상태

0.8배로 축소, 알파값 0 에서

 

1배로오면서 알파값 1을 줌으로써 

살짝 커지는 효과를 주고

애니메이션후 오토레이아웃을 잡아줌으로써

화면에 꽉차게 커지도록 구현한 코드에요

 

 

 

 

이렇게 만든 애니메이션 객체를

extension FirstViewController: UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
    // present될때 실행애니메이션
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return transition
    }
    // dismiss될때 실행애니메이션
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return DisMissAnim()
    }
}

첫번째 뷰컨트롤러에 프로토콜을 채택하고

설정해줍시다!!

 

 

여기서 DisMissAnim는

마찬가지로 사라지는 애니메이션 객체를 간단하게 만들었어요

class DisMissAnim: UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning{
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.5
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        // 사라지는 뷰(종료되는 뷰)
        guard let fromView = transitionContext.view(forKey: .from) else {
            return
        }
        UIView.animate(withDuration: 0.2, animations: {
            fromView.alpha = 0
        }) { (completed) in
            transitionContext.completeTransition(completed)
        }
    }
}

dismiss되면

두번째화면에서 (From)

첫번째화면으로 (To)

오니까

From을 참조해서 두번째화면이 꺼지니까

알파값을 0을 주면서 사라지도록 했어요

 

 

    @IBAction func buttonClick(_ sender: Any) {
        guard let secondVC = self.storyboard?.instantiateViewController(withIdentifier: "SecondVC") as? SecondViewController else {
            return
        }
        secondVC.transitioningDelegate = self
        secondVC.modalPresentationStyle = .custom
        present(secondVC, animated: true)
    }

그리고 하나더 중요한것은

(두번째화면)다음화면에도 transitioningDelegate를 설정해줘야해요

modalPresentationStyle은 custom으로 해주시구요!!

 

자 이제 끝났어요 어떻게 작동하나 볼까요???

 

 

 

 

동작 방식을 이해하는데 좀 헷갈렸었고

감을 잡는데 좀걸린거 같아요

그래도 하고나니까 이뻐보이네요 ㅎㅎ

이 맛에 애니메이션 하는가봐요

 

 

 

 

반응형