화면에서 가장 위에 올라와있는 ViewController 찾기(topViewController)

Doogie·2023년 3월 23일
0

문제 상황

화면 구성이 복잡하고 너무 많은 정보와 기능을 가지고 있는 View의 경우 view와 viewModel 각각 하나만 가지고 코드를 작성하기에 크기가 너무 커질 수 있다
-> 그래서 휴대폰에 보여지는 큰 뷰를 세분화 시킨 뒤 각각의 뷰가 뷰모델을 가질 수 있도록 프로젝트를 구성하였다

하지만...

여러개의 View로 VC가 구성되다보니 결국 화면 전환을 할 때는 최상위 ViewModel을 통해 VC를 거쳐야하니 결국에는 최상위 ViewModel과 VC는 크기가 커질 수 밖에 없는 상황이 됐다...

그때 문득 떠오르는 생각이
“NavigationController의 최상위 뷰를 찾는 것 처럼 현재 화면에서 가장 상단에 있는 VC를 찾아주는 메서드도 있지 않을까?”

하고 찾아본 결과 기본으로 제공되는 메서드는 없고 아래 코드를 통해 가장 상단에 있는 VC를 반환해 줄 수 있다고 한다

최상위 VC를 반환하는 코드

코드를 먼저 보자면 아래와 같은데...

extension UIApplication {
    static func topViewController(base: UIViewController? = UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }

        if let tab = base as? UITabBarController {
            if let selected = tab.selectedViewController {
                return topViewController(base: selected)
            }
        }

        if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }
        return base
    }
}

솔직히... 코드만 보고 잘 이해가 되지 않아서 콘솔에 찍어 보면서 작동 과정을 확인해봤다.ㅋㅋ

먼저 topViewController 메서드에서는 총 세 가지를 체크한다
1. base가 UINavigationController인지 (맞다면 NavigationController의 최상단 뷰를 base로 다시 topViewController 호출)
2. base가 UITabBarController인지 (맞다면 TabBarController에서 선택된 뷰를 base로 다시 topViewController 호출)
3. base가 present한 VC가 있는지 (있다면 해당 VC로 다시 topViewController 호출)

-> 이렇게 반복적으로 체크된 VC가 NavigationController도 아니고, TabBarController도 아니고 present한 VC가 없다면 최 상단에 띄워진 VC로 판단해 최종적으로 해당 VC를 반환하는 방식
(으로 이해했다… 맞겠쥬?)

현재 프로젝트에서의 적용

마이 페이지의 뷰 계층을 보면 MainTabbarVC -> NavigationController -> MyPageVC이다

이 중 MainTabbarVC는 LunchScreenVC에서 present되는 VC이다

즉, 마이 페이지가 화면에 보이는 순서는 LunchScreenVC에서 MainTabbarVC를 present -> MainTabbarVC안의 NavigationController가 MyPageVC를 보여주는 순이다

그래서 MyPageVC의 세분화된 View에서 topViewController를 호출하면

1. 첫 번째 호출

topViewController를 처음 호출 했을 때 base에 들어가는 기본값, KeyWindow의 rootViewController이며

이 값은 LunchScreenVC이다

  • LunchScreenVC는 navigationController인가? -> X
  • LunchScreenVC는 tabbarController인가? -> X
  • LunchScreenVC는 present한 뷰가 있는가? -> O, MainTabbarVC를 present하고 있다
    -> topViewController의 base를 TabbarVC로 다시 호출 한다

2. 두 번째 호출

topViewController의 두 번째 호출은 1번의 keyWindow가 present한 MainTabbarVC를 base로 호출 된다

  • MainTabbarVC는 navigationController인가? -> X
  • MainTabbarVC는 tabBarController인가 -> O
    -> MainTabbarVC에서 선택되어있는 VC를 base로 topViewController를 다시 호출한다

3. 세 번째 호출

topViewController의 세 번째 호출은 2번의 MainTabbarVC에서 선택된 VC를 base로 호출 된다
(NavigationController생성시 따로 프로퍼티를 만들지 않아서 이름은 없지만 어쨌든 navigationController…)

  • navigationController인가 -> O
    -> navigationController의 맨 위에 보여지는 VC를 base로 topViewController를 다시 호출한다

4. 네 번째 호출

topViewController의 네 번째 호출은 3번의 NavigationController의 맨 위에 보여지는 MyPageVC를 base로 호출된다

  • MyPageVC는 navigationController인가 -> X
  • MyPageVC는 tabBarController인가 -> X
  • MyPageVC는 present한 뷰가 있는가? -> X
    -> 요놈이 최 상위에 떠있는 뷰고 최종적으로 MyPageVC를 반환하게 된다

마무리

이렇게 해서 최종적으로 최상단에 떠있는 VC가 무었인지 알게 되었고 topViewController메서드를 통해 세분화된 뷰(UIView)에서도 다른 화면으로 화면전환을 할 수 있게 되었다

코드를 해석하는데도 머리가 아픈데 어떻게 이런 방법을 생각해내는건지 신기할 따름이다...

+ 네비게이션의 상단 뷰를 찾을 때 왜 visibleViewController는 쓰는지 의문

공식문서를 본 결과 topViewController는 가장 마지막스택에 있는 뷰(가장 마지막에 push된 뷰)이고 visibleViewController는 형식상 화면에 가장 맨 위에 있는 뷰라고 하는데 정상적으로 스택을 쌓지 않고 nvigation가장 위에 뷰가 올라 올 수 있어서 이걸 쓴건가?

(사실 어떻게 그런 경우가 발생할 수 있는지는 모르겠지만 이런 이유때문이라면 visibleViewController이 실제 화면에 보이는 뷰를 보여주기 적합할 수도 있겠다)

(여기서 말하는 topViewController는 위에 정의한 메서드가 아니라 UINavigationController의 메서드이다)

profile
끊임없이 문을 여는 개발자

0개의 댓글