Creating Custom Presentations
📌 Custom Transition 에 관한 배경 지식이 필요한 글입니다.
Presentation Controller
- presented VC 가 present 되고 dismiss 될 때까지 UIKit이 reference를 유지
- 역할
present, dismiss 에 대응
- presented VC 의 사이즈 설정
- presented content 의 시각적 모습을 바꾸기 위해 custom view 추가 (ex. dimmingView)
- custom view 에 transition animation 제공
(Animator 객체의 transition animation 과 동기화 가능)
- 앱 환경이 변경 될 때 presentation 의 시각적 모양 조정
ex) size class 가 compact 에서 regular 로 변경, rotation이 발생
1. Custom Presentation 사용하기
- VC 의 modalPresentationStyle 을 custom 으로 설정
- VC 의 transitioningDelegate 설정
@IBAction func menuButtonTapped(_ sender: Any) {
let secondVC = storyboard!.instantiateViewController(identifier: "SideVC")
secondVC.modalPresentationStyle = .custom
secondVC.transitioningDelegate = self
present(secondVC, animated: true)
}
2. Custom Presentation 절차
presentation controller 는 animator 객체와 함께 작동하여 전반적인 transition 구현
custom transition 을 구현할 때와 마찬가지로
transitioningDelegate를 통해 presentationController 객체를 UIKit 에게 제공
presentation controller, Animator 객체 모두 animation 을 구현할 수 있는 메서드가 제공됩니다.
주요한 차이점은 presentation controller 의 경우 present, dismiss 에 따라 호출되는 메서드가 분리돼서 제공되지만, Animator 객체의 경우 animate Transition 만 제공됩니다. 따라서 Animator 객체는 present 용도, dismiss 용도로 객체를 2개를 만들거나 혹은 하나의 객체에서 present, dismiss 를 분기처리하여 구현할 수 있습니다.제가 만든 예제에서는 분기처리 하는 방식으로 구현하긴 했지만, 객체를 나눠서 구현하는게 더 가독성이 좋아 보이고 애니메이션이 복잡해질 수록 더 효율적일 것 같습니다.
- custom presentation controller 를 얻기 위해 transitioning delegate 의 presentationController(forPresented:presenting:source:) 를 호출
- transitioning delegate 에게 animator, interactive animator 객체를 요청
- presentation controller 의 presentationTransitionWillBegin() 을 호출
-> 여기서 custom view 를 view hierarchy 에 추가하고 해당 view 에 대한 animation 을 구성해야 한다. - Animator 객체에서 transition animation 을 수행
- presentationTransitionDidEnd(_:) 호출
-> presentation animation 이 끝나면 presentation 에게 알림
3. Presentation Controller 의 프로퍼티 및 메서드
3-1. Presented VC 의 frame 설정
-
frameOfPresentedViewInContainerView
presented VC 는 기본적으로 container view 의 frame 을 꽉 채우지만,
frameOfPresentedViewInContainerView 프로퍼티를 이용하여 frame 을 설정함으로써
화면의 일부만 채울 수 있다.animator 객체는 frameOfPresentedViewInContainerView 에 의해 반환된 frame 값으로 presented VC 를 애니메이션 하는 역할을 한다.
frameOfPresentedViewInContainerView 로 설정한 PresentedView 의 frame 값은 Animator 객체의 animateTransition 메서드 안에서 finalFrame(forKey: toVC) 을 통해 값을 얻고 활용할 수 있다.
ex) container view 의 절반만 덮도록 frame 값을 변경하는 예시
override var frameOfPresentedViewInContainerView: CGRect {
var presentedViewFrame: CGRect = .zero
let containerBounds = containerView!.bounds
presentedViewFrame.size = CGSize(width: containerBounds.width / 2,
height: containerBounds.height)
return presentedViewFrame
}
3-2. Custom views 관리 및 애니메이션
presentation controller 는 presentation 과 관련된 모든 custom view 들을 생성하고 관리하는 책임을 가짐
- init
일반적으로 init 에서 custom view 들을 생성한다.
ex) presentation controller 의 init 에서 gesture recognizer 를 추가한 dimming view 를 만드는 예제
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
dimmingView = UIView()
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
dimmingView.translatesAutoresizingMaskIntoConstraints = false
dimmingView.backgroundColor = UIColor(white: 0, alpha: 0.1)
dimmingView.alpha = 0
let recognizer = UITapGestureRecognizer(target: self, action: #selector(dimmingViewTapped))
dimmingView.addGestureRecognizer(recognizer)
}
@objc
func dimmingViewTapped() {
presentingViewController.dismiss(animated: true)
}
- presentationTransitionWillBegin
custom view 들을 구성하고 container view 에 추가
presented 혹은 presenting VC 의 transition coordinator 를 이용하여 애니메이션을 생성
override func presentationTransitionWillBegin() {
let containerViewBounds = containerView!.bounds
dimmingView.frame = containerViewBounds
containerView?.insertSubview(dimmingView, at: 0)
guard let coordinator = presentedViewController.transitionCoordinator else {
dimmingView.alpha = 1
return
}
coordinator.animate { _ in
self.dimmingView.alpha = 1
}
}
- animate(alongsideTransition:completion:)
animator 객체에서 처리되지 않는 애니메이션을 처리하고자 할 때 사용
animate alongside 의 애니메이션 블럭 내의 코드는 animator 객체의 animateTransition 메서드 내에서 UIView.animate 와 동시에 수행 (즉, transition animation 과 동시에 수행된다는 뜻)
하지만 Core Animation 으로 animate 하는 경우에는 animateTransition 메서드가 리턴되고 나서 수행된다.
- 정리하자면,
presentation controller 에서 추가한 custom view 에 대해 animation을 추가하고자 할 때, animator 객체의 transition animation 과 동기화시켜야 할 것입니다. 그럴 때 사용할 수 있는 메서드입니다.
transition coordinator 에서도 동일하게 기술하고 있습니다.
- containerView
transition 과정에서 사용되는 view 들의 superView 로써,
containerview 는 presentation controller, animator 객체 둘 다 공용으로 사용
(애니메이션 되는 view 들은 모두 container view의 subview 여야 합니다. 때문에 위 예제에서도 presentation controller 에서 dimming view 를 animate 하기 전 container view 의 subView 로 만들어줍니다)
- presentationTransitionDidEnd
presentation 이 끝나면 presentationTransitionDidEnd 를 이용하여 presentation 취소로 인한 정리(clean up)를 처리한다.
override func presentationTransitionDidEnd(_ completed: Bool) {
if !completed {
dimmingView.removeFromSuperview()
}
}
- dismissalTransitionWillBegin
dismiss 때 호출되는 메서드.
override func dismissalTransitionWillBegin() {
guard let coordinator = presentedViewController.transitionCoordinator else {
dimmingView.alpha = 0.0
return
}
coordinator.animate(alongsideTransition: { _ in
self.dimmingView.alpha = 0.0
})
}
- dismissalTransitionDidEnd
override func dismissalTransitionDidEnd(_ completed: Bool) {
if completed {
dimmingView.removeFromSuperview()
}
}
-
Custom Presentation 만 적용 시
-
Custom Presentation + Custom Transition
소스코드:
https://github.com/tksrl0379/CustomTransition-Presentation
아래는 Custom Presentation & Transition 의 핵심 객체인
Transition Delegate, Presentation Controller, Animator 의 코드입니다.
전체적인 흐름은 주요 메서드들에 표시한 숫자의 순서에 따라 진행됩니다.
소스코드 전문을 보고 싶으시면 위 링크를 참고해주세요.
Transition Delegate (Presentation controller, Animator 제공)
extension MainVC: UIViewControllerTransitioningDelegate {
// MARK: Custom Presentation ( Presentation Controller )
// 1
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
CustomPresentationController(presentedViewController: presented, presenting: presenting)
}
// MARK: Custom Transition ( Animator Object )
// 2
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return CustomAnimator(isPresenting: true)
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return CustomAnimator(isPresenting: false, interactionController: (dismissed as? SideVC)?.swipeInteractionController)
}
}
Presentation Controller (Presentation 관리)
class CustomPresentationController: UIPresentationController {
private var dimmingView: UIView
override var frameOfPresentedViewInContainerView: CGRect {
var presentedViewFrame: CGRect = .zero
let containerBounds = containerView!.bounds
presentedViewFrame.size = CGSize(width: containerBounds.width * 0.8,
height: containerBounds.height)
return presentedViewFrame
}
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
dimmingView = UIView()
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
dimmingView.translatesAutoresizingMaskIntoConstraints = false
dimmingView.backgroundColor = UIColor(white: 0, alpha: 0.2)
dimmingView.alpha = 0
let recognizer = UITapGestureRecognizer(target: self, action: #selector(dimmingViewTapped))
dimmingView.addGestureRecognizer(recognizer)
}
// MARK: Presentaiton
// 3
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
let containerViewBounds = containerView!.bounds
dimmingView.frame = containerViewBounds
dimmingView.alpha = 0
containerView?.insertSubview(dimmingView, at: 0)
guard let coordinator = presentedViewController.transitionCoordinator else {
dimmingView.alpha = 1
return
}
// animator 객체의 animateTransition 메서드 내의 UIView.animate 와 동시에 수행
coordinator.animate { _ in
// 6
self.dimmingView.alpha = 1
}
}
override func presentationTransitionDidEnd(_ completed: Bool) {
super.presentationTransitionDidEnd(completed)
}
// MARK: Dismissal
override func dismissalTransitionWillBegin() {
guard let coordinator = presentedViewController.transitionCoordinator else {
dimmingView.alpha = 0
return
}
coordinator.animate(alongsideTransition: { _ in
self.dimmingView.alpha = 0
})
}
}
extension CustomPresentationController {
@objc
func dimmingViewTapped() {
presentingViewController.dismiss(animated: true)
}
}
Animator 객체 (Transition Animate 관리)
class CustomAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let interactionController: SwipeInteractionController?
private let isPresenting: Bool
private let duration: TimeInterval = 0.15
init(isPresenting: Bool, interactionController: SwipeInteractionController? = nil) {
self.isPresenting = isPresenting
self.interactionController = interactionController
super.init()
}
// 4
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
duration
}
// 5
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let firstKey: UITransitionContextViewControllerKey = isPresenting ? .from : .to
let secondKey: UITransitionContextViewControllerKey = isPresenting ? .to : .from
let mainVC = transitionContext.viewController(forKey: firstKey)!
let sideVC = transitionContext.viewController(forKey: secondKey)!
if isPresenting {
containerView.addSubview(sideVC.view)
}
let endFrame = transitionContext.finalFrame(for: sideVC)
var startFrame = endFrame
startFrame.origin.x -= startFrame.width
let initialFrame = isPresenting ? startFrame : endFrame
let finalFrame = isPresenting ? endFrame : startFrame
sideVC.view.frame = initialFrame
UIView.animate(withDuration: duration, delay: 0, options: .curveLinear) {
// 6
sideVC.view.frame = finalFrame
mainVC.view.frame.origin.x += (self.isPresenting ? finalFrame.width : -finalFrame.width)
} completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
- 참고 문서
View Controller Programming Guide for iOS
https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/DefiningCustomPresentations.html#//apple_ref/doc/uid/TP40007457-CH25-SW1
UIPresentationController Tutorial: Getting Started
https://www.raywenderlich.com/3636807-uipresentationcontroller-tutorial-getting-started
Author And Source
이 문제에 관하여(Creating Custom Presentations), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@tksrl0379/Creating-Custom-Presentations저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)