UIViewController를 Modal로 표시하는 것이 대해 글을 작성해 보려고 합니다.
"modal로 화면을 띄운다,,, modal로 표시를한다" 등 모달이라는 말이 화면과 관련된 말임은 알고 있었습니다.
하지만 정확히 어떤 목적을 의미하는 지는 생각하지 못했던 것 같습니다.
"modal로 띄운다는 진행중이던 흐름을 잠시 뭠추고, 새로운 작업이 생기며 그것을 해결해야 이전 플로우로 돌아갈 수 있다"
이 정도로 이해할 수 있습니다.
이렇게 이해하면 Alert를 왜 Modal로 방식으로 화면에 표시하는지 이해가됩니다.
진행중인 플로우를 끊고 잠시 Alert창을 유저가 확인해야하기 때문이죠.
UIVIewController의 present함수를 사용하면 Modal로 화면을 띄울 수 있습니다.
여기서 화면을 표시하는 방법중 두가지 옵션을 설정할 수 있는데요,
바로 modalPresentationStyle과 modalTransitionStyle프로퍼티를 조정하는 것입니다.
PresentationStyle은 말그대로, 화면이 어떻게 표시될 것인가를 결정합니다.
modal로 등장한 화면의 최종형태를 말합니다.
Transition을 말그대로 전환방법을 의미합니다.
어떻게 설정한 프레젠테이션 화면으로 전활될 것인가에 대한 설정입니다.
PresntingViewController, PresentedViewController
ViewController는 위와 같은 프로퍼티를 가집니다.
해당 프로퍼티는 ViewController가 present를 사용해, ViewController를 modal로 표시한 경우 할당됩니다.
modal 화면을 표시한 ViewController의 프로피티를 기준으로
modal로 표시된 화면의 ViewController를 presentedViewController라고 합니다.
modal을 표시한 뷰컨트롤러를 기준으로 PresentingViewController는 자신을 표시한 ViewController입니다.
🤔 하지만 present를 사용했다고 반드시 presentingViewController가 되는 것은 아닙니다.
presentingViewController는 현재 modalViewController가 속한 뷰컨트롤러 계층을 관리하는 ViewController로 지정됩니다.
만약, BaseViewController가 NavigationController의 스택의 일원이라면 어떻게 될까요?
뷰컨트롤러 계층을 관리하는 것은 NavigationController임으로 presentingViewController는 NavigationController로 지정됩니다.
실제로 아래 그림과 같은 구조를 가지게됩니다.
이 경우 주의할 점이 있는데요, 해당 상태에서 BaseViewController를 네비게이션 스택에서 pop해도
ModalViewController는 사라지지 않는다는 점입니다.
따라서 ViewController를 네비게이션 스택에서 pop하는 경우
반드시 modal화면을 모두 제거한 상태에서 pop해야 예기치 못한동작을 예방할 수 있습니다.
두개의 ViewController를 modal할 수 없다.
하나의 ViewController는 하나의 ViewController를 Modal창으로 표시할 수 있습니다.
만약 2개를 연속으로 present하는 경우 경고가 표시되고 이후에 present한 ViewController는 modal로 표시되지 않습니다.
Transitioning
이어서 전환에 대해 다뤄보겠습니다.
modalTransitionStyle를 변경하는 아래 영상처럼 ViewController가 등장하는 방식에 영향을 줍니다.
Modal 화면 전환 / 표시 커스텀화
Presentation 과 Transition은 커스텀화 할 수 있습니다.
UIKit은 transitioningDelegate프로퍼티에 할당된 UIViewControllerAnimatedTransitioning객체를 통해
PresentationController를 생성합니다.
따라서 해당 프로퍼티에 컨트롤러 객체를 할당함으로써 커스터마이징이 가능합니다.
class CustomPresentationController: NSObject, UIViewControllerTransitioningDelegate {
/// Prsentation
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
let pvc = UIPresentationController(
presentedViewController: presented,
presenting: presenting
)
// presentationStyle = .custom인 경우 호출된다.
// 활용방안은 아직 학습이 필요하지만, 이곳에서 UIPresentationController를 수정하고 반환할 수 있다.
return pvc
}
/// transition - appear
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> (any UIViewControllerAnimatedTransitioning)? {
FadeOutAnimator()
}
/// transition - dismiss
func animationController(forDismissed dismissed: UIViewController) -> (any UIViewControllerAnimatedTransitioning)? {
FadeInAnimator()
}
}
Navigation push애니메이션 커스텀화
네비게이션 위임자를 구현하여 해당 동작을 변경할 수 있다.
class CustomNavigationControllerDelegate: NSObject, UINavigationControllerDelegate {
func navigationController(
_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationController.Operation,
from fromVC: UIViewController,
to toVC: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
return FadeOutAnimator() // Custom animation object
}
}
// delegate는 weak참조임으로, navDelegate는 외부에서 참조하도록 해야한다.
let navDelegate = CustomNavigationControllerDelegate()
navigationController?.delegate = navDelegate
Animator?
위코드에서 보였던 애니메이터 객체들은 UIViewControllerContextTransitioning객체로
UIViewController의 Transition애니메이션등을 커스텀화 할 수 있는 객체이다.
public class FadeInAnimator: NSObject, UIViewControllerAnimatedTransitioning {
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.35 // 애니메이션 지속 시간
}
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: .from) else { return }
let containerView = transitionContext.containerView
containerView.addSubview(fromView)
// 애니메이션 시작 상태 설정
fromView.alpha = 1.0
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: {
// 애니메이션 적용
fromView.alpha = 0.0
}) { _ in
// 애니메이션 완료 후 처리
fromView.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
public class FadeOutAnimator: NSObject, UIViewControllerAnimatedTransitioning {
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.35 // 애니메이션 지속 시간
}
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toView = transitionContext.view(forKey: .to) else { return }
let containerView = transitionContext.containerView
containerView.addSubview(toView)
// 애니메이션 시작 상태 설정
toView.alpha = 0.0
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: {
// 애니메이션 적용
toView.alpha = 1.0
}) { _ in
// 애니메이션 완료 후 처리
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
transitionContext는 전환시 발생하는 모든 데이터를 가지고 있다.
transitionContext를 사용해 원한는 정보를 추출하고 transition을 커스텀화 할 수 있다.
containerView는 transition에 관여하는 View들의 SuperView처럼 해동하는 View이다.
화면전환 후까지 유지되는 View로 반드시 등장하는 화면(appear시 to키를 통해 추출한 toView)는
반드시 해당 containerView의 서브뷰여야 화면에 표시된다.
(dismiss시 해당 뷰와의 계층관계를 끊어야 한다.)
커스텀 트렌지션이 종료되었다면 반드시 시스템에 종료됬음을 알려야한다.
transitionContext.completeTransition를 통해 알릴 수 있으며, 성공적으로 종료됬는지 여부를 Bool타입으로 알릴 수 있다.
만약 false를 전달하는 경우 트렌지션은 곧바로 종료되고,
예를들어 특정 뷰컨트롤러가 등장하는 경우, 기존의 ViewController가 다시 표시된다. (즉, 뷰컨트롤러 전환이 취소된다.)
회고
Modal은 무엇인가?에 대해 생각정리를 할 수 있었다.
UIViewController의 화면전환에 대해 깊게 알게 되었으며, 커스터마이징이 깊이있게 가능함을 알 수 있었다.
InteractiveTransition을 통해 유저와 실시간 상호작용을 통해 전환을 관리할 수 있는데 추후에 한번 학습해 보고 싶다.
네비게이션 스택과 모달화면을 모두 사용하는 경우 앞서 언급한 상황을 조심하자. (모달창이 사라지지 않는 케이스)
'UIKit' 카테고리의 다른 글
[UIkit] 키보드 어보이던스(with 카카오톡 채팅방 만들기) (0) | 2024.06.27 |
---|---|
[UIKit] 커스텀 탭바 만들기 (0) | 2024.04.29 |
[UIKit] convert매서드를 이용한 상대 좌표 구하기 (0) | 2024.04.01 |
[UIKit] UIKit 앱실행부터 UIView까지(iOS 13 ⬆) (0) | 2024.03.12 |