저번 포스팅에서는 어려운 단계에 도달하기 전에 쉬어가는 시간으로 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 는 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 자세한 코드 설명은 생략합니다.
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 를 호출합니다.
처음 접하는 분에게는 위의 과정은 상당히 복잡하고 어려운 것이 정상입니다.
이번 주제에서는 SourcePresentAnimator, SourceDismissAnimator에 대한 자세한 설명을 다루지 않았습니다.
그 이유는 UIViewController Interactive 의 가장 핵심이며
직접 자신이 생각한 대로 구현해보는 것이 지식을 습득하는데 많은 도움이 되기 때문입니다.
각자 자신이 생각하는 Animator object 를 구현해보시길 바랍니다.
마무리
이제 사용성이 좋은 앱을 만들기 위해 마지막 발자국을 걸었습니다.
여기까지 오신분들께 정말 감사하다는 인사를 드립니다.
이 포스트를 마지막으로 iOS 사용성 최대로 올려보자! 시리즈를 마무리합니다.
소중한 시간 내어서 읽어주셔서 감사합니다
'iOS' 카테고리의 다른 글
왜 MVVM 을 사용할까요? (0) | 2019.09.03 |
---|---|
RxCocoa Binder? ControlEvent? (0) | 2019.09.01 |
iOS 사용성 최대로 올려보자! (Part5) - Easing function (1) | 2019.08.19 |
iOS 사용성 최대로 올려보자! (Part3) - UIView Interactive Animation (1) | 2019.08.19 |
iOS 사용성 최대로 올려보자! (Part4) - UIViewController Non Interactive Transition (0) | 2019.08.18 |