본문 바로가기

iOS

iOS 사용성 최대로 올려보자! (Final) - UIViewController Interactive Transition

저번 포스팅에서는 어려운 단계에 도달하기 전에 쉬어가는 시간으로 Easing Function 을 살펴보았습니다.

 

이 포스팅은 "사용성 최대로 올려보자!"의 Series  중 일부입니다.

 

1.  iOS 사용성 최대로 올려보자! (Part1) - PaintCode

2. iOS 사용성 최대로 올려보자! (Part2) - ShapeShifter

3. iOS 사용성 최대로 올려보자! (Part3) - UIView Interactive Animation

4. iOS 사용성 최대로 올려보자! (Part4) - UIViewController Non Interactive Transition

5. iOS 사용성 최대로 올려보자! (Part5) - Easing function

6. iOS 사용성 최대로 올려보자! (Final) - UIViewController Interactive Transition

 

우선 먼저 Demo 를 살펴보도록 하죠!

 

Dragging 에 따라서 UIViewController 가 Transition 되는 것이 보이시나요? 

 

시작하기 전에 아래 시퀀스 다이어그램을 다시 떠올려 봅시다!

 

 

이제 시작하겠습니다!

 

UICollectionView 에 어떤 Item 이 선택되면 CustomImageViewController  를 띄우도록 하고 있습니다.

 

let vc = CustomImageViewController.initFromStoryboard(name: "Main")
        vc.modalPresentationStyle = .custom
        let dismissAnimator = SourceDismissAnimator(with: collectionView, for: indexPath, id: "image_\(indexPath.item)")
        let uiPresentation = UIInteractablePresentationController(presentedViewController: vc, presenting: self)
        fadeTransition = TransitioningDelegate(
            presentTransition: SourcePresentAnimator(with: collectionView, for: indexPath, id: "image_\(indexPath.item)"),
            dismissTransition: dismissAnimator,
//            dismissInteraction: InteractivePercentDrivenTransition(gesture: uiPresentation.panGestureRecognizer, presented: vc),
            dismissInteraction: InteractiveTransition(gesture: uiPresentation.panGestureRecognizer, animator: dismissAnimator, presented: vc),
            presentationController: uiPresentation
        )
        vc.transitioningDelegate = fadeTransition
        vc.modalPresentationStyle = .custom
        self.navigationController?.delegate = fadeTransition
        vc.indexPath = indexPath

        self.present(vc, animated: true)

 

1. CustomImageViewController transitioningDelegate 를 설정해 줍니다.

 

UIViewControllerTransitionDelegate, UINavigationContollerDelegate, UITabBarControllerDelegate 3가지 타입을 구현했던 기억이 있으시죠? 

 

번거로운 작업을 한방에서 TransitionDelegate에서 해결합니다.

 

...더보기

아래의 Parameter 를 전달하면 상황에 따라 Transition 객체를 UIKit에 전달합니다.

public init(presentTransition: UIViewControllerAnimatedTransitioning? = nil,
                dismissTransition: UIViewControllerAnimatedTransitioning? = nil,
                presentInteraction: UIViewControllerInteractiveTransitioning? = nil,
                dismissInteraction: UIViewControllerInteractiveTransitioning? = nil,
                presentationController: UIPresentationController? = nil)

 

2. self.present(CustomImageViewController) 를 호출합니다.

 

present를 하면 가장 먼저 호출되는 메소드는 UIInteractablePresentationController presentationTransitionWillBegin 입니다.

 

...더보기

Creating Custom Presentations  이해를 하고 오시면 좋습니다.

 

 

지금 예제에서 UIInteractablePresentationController 를 구현한 이유는 UIPanGestureRecognizer를 presentedView에 등록하기 위합니다.

CustomImageViewController 내부에서 UIPanGestureRecognizer 등록하는 것을 피하기 위함입니다. 

 

override func presentationTransitionWillBegin() {
        super.presentationTransitionWillBegin()
        guard let presentedView = presentedView else { return }
        presentedView.addGestureRecognizer(panGestureRecognizer)
    }

 

3. UIKit 은 animationController(forPresented:presenting:source 호출하고 animator object 가 있는지 Query 합니다.

 

open func animationController(forPresented presented: UIViewController,
                                  presenting: UIViewController,
                                  source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return presentTransition
    }

 

위에 TransitionDelegate를 설정할 때 presentTransition 를 Argument로 전달하였습니다. 

SourcePresentAnimator

 

SourcePresentAnimator 자세한 코드 설명은 생략합니다.

 

SourcePresentAnimator 는 CollectionView 의 선택된 IndexPath 를 TargetView 로 선정하고 다음에 보여질 CustomImageViewController ImageView frame과 동일하게 전환을 합니다.

 

4. present 를 할 경우에는 interactive animator object 를 nil로 반환합니다.

 

open func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return presentInteraction
    }

 

이제 Present 과정의 설명은 끝나고 Dismiss 의 설명을 시작합니다.

 

1. CustomImageViewController 의 ImageView 를 dragging 을 시작합니다.

 

UIPanGestureRecognizer 의 .began 상태가 오면 CustomImageViewController dismiss  를 실행합니다.

 

@objc private func onGestureRecognized(_ gesture: UIPanGestureRecognizer) {

        switch gesture.state {

        case .began:

            self.presented?.dismissViewController()

}

 

2. UIKit dismissal animator object 가 있는지 Query 합니다.

 

위에 TransitionDelegate를 설정할 때 dismissTransition를 Argument로 전달하였습니다. 

SourceDismissAnimator

 

SourceDismissAnimator 자세한 코드 설명은 생략합니다.

 

    open func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return dismissTransition
    }

 

3. UIKit 은 animator object nil 이 아님을 확인하고 interactive animator object 가 있는지 Query 합니다.

 

    open func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return dismissInteraction
    }

 

위에 TransitionDelegate를 설정할 때 dismissInteraction 를 Argument로 전달하였습니다. 

InteractivePercentDrivenTransition  or InteractiveTransition

...더보기

UIPercentDrivenInteractiveTransition 는 Percent 기반으로 interactive transition 를 제공합니다.

open class UIPercentDrivenInteractiveTransition : NSObject, UIViewControllerInteractiveTransitioning

 

InteractiveTransition 의 역할은 UIPanGestureRecognizer 상태에 따라 transition의 update, cancel, finish 를 수행합니다.

 

5. UIKit 은 Interactive Animation이 설정됨을 알고 startInteractiveTransition:transitionContext 를 호출합니다.

 

startInteractiveTransition:transitionContext 에서는 animator object 를 초기화하고 animation의 상태를 inactive 로 상태로 전환합니다.

 

6. UIKit 은 transition이 종료되기를 기다립니다. 

InteractiveTransition의 UIPanGestureRecognizer .ended .cancelled 상태가 전달되면 transition 을 종료합니다.

 

7. transition이 종료되면 UIKit 은 animationEnded 를 호출합니다.

 

 

처음 접하는 분에게는 위의 과정은 상당히 복잡하고 어려운 것이 정상입니다.

 

이번 주제에서는 SourcePresentAnimatorSourceDismissAnimator에 대한 자세한 설명을 다루지 않았습니다. 

 

그 이유는 UIViewController Interactive 의 가장 핵심이며 

직접 자신이 생각한 대로 구현해보는 것이 지식을 습득하는데 많은 도움이 되기 때문입니다.

 

각자 자신이 생각하는 Animator object 를 구현해보시길 바랍니다.

 

마무리

이제 사용성이 좋은 앱을 만들기 위해 마지막 발자국을 걸었습니다.

 

여기까지 오신분들께 정말 감사하다는 인사를 드립니다.

 

이 포스트를 마지막으로 iOS 사용성 최대로 올려보자! 시리즈를 마무리합니다.

 

SourceCode

 

소중한 시간 내어서 읽어주셔서 감사합니다