[iOS] 화면전환 애니메이션 Custom - UIViewControllerAnimatedTransitioing, UIViewControllerTransitioningDelegate

2020. 12. 20. 23:52iOS

반응형

오늘은 화면 전환 애니메이션을 커스텀하는 방법에 대해 알아볼거에요~~

대표적으로 App Store에 CollectionViewCell의 클릭했을 때, 화면이 커지면서 전환되는 효과가 대표적일 것 같아요.

 

저도 이 효과를 보면서 이건 어떻게하지...? 궁금했었는데 자료를 찾아보고 한 번 커스텀을 해보았습니다.

 

그렇다면 우선 어떻게 사용하고 어떤 기능을 하는지 알아볼게요 🤗

 

우선 사용하기 위해 사용해야하는 두 가지 대표적인 객체가 있습니다.

  • UIViewControllerAnimatedTransitioning
  • UIViewControllerTransitioningDelegate

 

그렇다면 각각의 역할을 알아볼까요.

 

 

UIViewControllerAnimatedTransitioning

애플 개발자 문서에 의하면 커스텀 뷰 컨트롤러 전환 애니메이션 효과를 위한 메소드의 집합이라고 해요. 

실제로 구현부를 보면 프로토콜로 정의되어 있고 그 안의 메소드를 구현해서 저희가 원하는 효과를 만들어낼 수 있어요.

 

대표적으로 꼭 구현해야하는 2가지의 메소드가 있어요.

  • transitionDuration(using:) - 커스텀 애니메이션 효과의 지속시간을 지정할 수 있는 메소드입니다.
  • animateTransition(using:) - 이 메소드를 통해 저희가 원하는 효과를 직접적으로 커스텀할 수 있어요, using을 통해 받는 context을 이용해서 어떤 화면을 표시하고 보일지 결정하게 됩니다.

여기서 정의하게 될 구현부는 다음과 같아요.

class CustomAnimator: NSObject, UIViewControllerAmimatedTransitioning {
    let duration = 0.8

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    	return duration
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    }
}

 

 

UIViewControllerTransitioningDeleagte

애플 개발자 문서에 의하면 뷰 컨트롤러 간의 전환을 관리하기 위한 집합이에요.

이 역시도 프로토콜로 정의되어 있고 안의 효과를 구현함에 따라 해당 커스텀한 뷰 전환효과를 사용할 수 있어요.

 

여기서 저희가 커스텀한 UIViewControllerAnimatedTransitioning을 이용해서 효과를 줄거에요.

 

대표적인 2가지의 메소드를 사용했어요.

  • animationController(forPresneted:, presenting:, source) - present 액션 수행 시, 전달할 커스텀 애니메이션을 지정할 수 있어요
  • animationController(forDismissed:) - dismiss 수행 시, 전달할 커스텀 애니메이션을 지정할 수 있어요

 

여기도 역시 구현부를 보면 다음과 같이 사용할 거에요.

extension ViewController: UIViewControllerTransitioingDelegate {
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioing? {
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    }
}

 


구현

이제 직접적인 구현에 들어가볼게요~~

먼저 UIViewControllerAnimatedTransitioning을 구현할게요.

class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    let duration = 0.8
    var presenting = true
    var originFrame = CGRect.zero
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        let toView = transitionContext.view(forKey: .to)!
        
        toView.clipsToBounds = true
        toView.layer.cornerRadius = 20
    
        containerView.addSubview(toView)
        containerView.bringSubviewToFront(toView)
        toView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
        toView.frame = originFrame
        
        UIView.animate(withDuration: duration,
                       delay: 0,
                       usingSpringWithDamping: 0.5,
                       initialSpringVelocity: 0.2,
                       animations: {
                        detailView.transform = .identity
                        detailView.frame.center = containerView.frame.center
                        detailView.frame.size = containerView.frame.size
                       }, completion: { _ in
                        transitionContext.completeTransition(true)
                       })
        
    }
}

 

 

우선 위의 transitionDuration 메소드의 경우에는 현재 지정될 화면 전환효과가 몇 초동안 실행될건지를 결정하는 메소드입니다.

 

그리고 이제 그 밑의 animateTransition의 경우에 현재 context을 바탕으로 어떻게 애니메이션 될 건지를 커스텀하게 지정해줄 수 있습니다.

 

우선 처음으로 transitionContext.containerView을 이용해서 현재 커스텀 애니메이션이 실행될 화면의 containerView을 얻어와야해요.

그리고 view(forKey: .to)을 이용해서 다음 화면의 view을 얻어올 수 있어요. 

이를 얻어와서 containerView에 보여져야겠죠?

 

그러면 originFrame의 경우는 아직 이해가 안될 수 있어요.

간단하게 설명하면 CollectionViewCell을 클릭할 건데, 저희는 그 클릭한 Cell의 위치부터 애니메이션을 시작하기 때문에 그 Cell의 Frame이라고 알면될 것 같아요. ViewController에서 설정해줄거랍니다.

 

그리고 마지막으로 뒤의 animation은 다음 뷰의 애니메이션을 자유롭게 커스텀하면 될 것 같아요.

 

이제 커스텀 애니메이션은 구현되었습니다.

 

하지만, 지금 적용하면 적용이 안되겠죠?

아직, 어디서 동작하게 할지 설정을 안했기 때문이죠.

 

이제 ViewController의 구현부로 넘어갑니다.

class ViewController: UIViewController {
    let animator: CustomAnimator = CustomAnimator()
    
    // -------------------- 구현부 -----------------------
}

extension ViewController: UIViewControllerTransitioningDelegate {
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        guard let selectedIndexPath = mainCollectionView.indexPathsForSelectedItems,
              let selectedCell = mainCollectionView.cellForItem(at: selectedIndexPathCell.first!) as? CustomCell,
              let selectedCellSuperView = selectedCell.superView else { return nil }
              
        animator.originFrame = selectedCellSuperView.convert(selectedCell.frame, to: nil)
        return animator
    }                         
}

extension ViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        guard let detailVC = self.storyboard?.instantiateViewController(withIdentifier: DetailVC.identifier)
                as? DetailVC else { return }
        detailVC.image = foodData[indexPath.row]
        detailVC.modalPresentationStyle = .overFullScreen
        detailVC.transitioningDelegate = self
        
        self.present(detailVC, animated: true, completion: nil)
    }
}

이제 ViewController에서 저희가 커스텀해준 Animator 객체를 생성하고 사용하여야 합니다.

여기서 이 객체를 present가 일어날 때, delegate를 통해 넘겨주게 됩니다.

그렇게 되면 저희가 커스텀 한 애니메이션이 실행되는 것을 확인할 수 있습니다.

 

animationController(forPresneted:,presenting:,source:)을 보게되면 originFrame에 대해 지정해주는 것이 보이죠⁉️

originFrame의 경우 위에서도 설명했지만 Cell의 window에 대비한 frame의 위치입니다. 

 

저기서 convert를 사용하는 이유는 Cell은 CollectionView을 기준으로 frame을 알고있습니다.

그러나 현재 알고싶은건 UIWindow를 기준으로 한 frame의 위치이기 때문에, convert를 실행합니다.

 

그리고 return 값으로 animator을 주게되면 이제 원하는 애니메니션이 적용이 됩니다.

 

마지막으로 화면이 전활될 때, 해당 ViewController의 transitionDelegate을 적용하기만 하면 됩니다.

 

 

저렇게 적용하게 되면

다음과 같은 효과를 확인할 수 있습니다.

dismiss도 원할 경우 원하는 대로 커스텀 할 수 있습니다.

 

평소 어렵게 생각했었는데 이렇게 만들면 App Store와 동일한 효과로 화면효과를 줄 수 있습니다.

 

혹시 틀린 점이나 궁금한 점있으면 댓글로 남겨주세요~~

감사합니다~‼️

반응형

'iOS' 카테고리의 다른 글

[iOS] CABasicAnimation란  (0) 2020.12.31
[iOS] CALayer란  (2) 2020.12.31
[iOS] CollectionView Carousel 3D Effect  (0) 2020.12.13
[iOS] UICollectionViewLayout 설정  (0) 2020.12.06
[iOS] Version + Build 이용 프로젝트 관리  (0) 2020.11.28