SwiftUI에서 UIKit의 `UINavigationController`로 화면 전환 구현하기

2024. 11. 7. 13:00·iOS/SwiftUI

SwiftUI는 매우 직관적이고 선언적인 UI 구성 방식을 제공하지만, iOS 14 기준으로 제공되는 네비게이션 도구에는 아직 제약이 있어 화면 전환을 보다 유연하게 제어하는 데 어려움을 겪을 수 있습니다. 특히, 복잡한 네비게이션 구조를 필요로 하거나 UIKit에서 제공하는 네비게이션 기능을 활용하고 싶을 때 SwiftUI의 NavigationView와 NavigationLink는 부족한 면이 있습니다.

SwiftUI를 통해 프로젝트를 진행하던 중 화면 전환의 어려움을 겪었고, 결국 UIKit의 UINavigationController를 활용해 SwiftUI와 UIKit 네비게이션을 조합하여 구현하는 방식을 도입하게 되었습니다. 이 글에서는 UINavigationController를 SwiftUI에서 사용하는 방법과 최상단 뷰 컨트롤러를 찾아 새로운 화면을 표시하는 방식에 대해 설명드리겠습니다.


1. UIKit의 UINavigationController를 SwiftUI에서 사용하는 방법

먼저, UIKit의 UINavigationController를 SwiftUI에 통합하기 위해 UIViewControllerRepresentable을 사용하여 UIKit 네비게이션 컨트롤러를 SwiftUI로 감싸는 작업이 필요합니다. UIViewControllerRepresentable은 SwiftUI와 UIKit 간의 상호 작용을 가능하게 해주는 프로토콜로, UIKit 뷰 컨트롤러를 SwiftUI에 맞게 재구성할 수 있도록 합니다.

UINavigationView 구조체 생성

UIKit의 UINavigationController를 감싸기 위한 UINavigationView라는 간단한 구조체를 작성합니다. 이 구조체는 UIViewControllerRepresentable을 준수하고, NavigationManager라는 클래스를 활용해 화면 전환을 제어합니다.

import SwiftUI
import UIKit

struct UINavigationView: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UINavigationController {
        let rootViewController = UIHostingController(rootView: RootView())
        let navigationController = UINavigationController(rootViewController: rootViewController)

        // 싱글톤 NavigationManager에 navigationController를 주입
        NavigationManager.shared.navigationController = navigationController
        return navigationController
    }

    func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
        // 필요할 경우 업데이트 로직 구현
    }
}

NavigationManager 클래스 정의

화면 전환을 제어하기 위해 NavigationManager라는 클래스를 만들어 줍니다. 이 클래스는 ObservableObject를 준수하며, 두 가지 push 메서드를 제공하여 SwiftUI의 View와 UIKit의 UIViewController 모두를 유연하게 화면 전환할 수 있도록 합니다.

import UIKit
import SwiftUI

class NavigationManager: ObservableObject {
    static let shared = NavigationManager()  // 싱글톤 인스턴스 생성
    weak var navigationController: UINavigationController?

    private init() {}  // 외부에서 인스턴스 생성 방지

    // SwiftUI View를 받는 push 함수
    func push<V: View>(_ view: V) {
        let hostingController = UIHostingController(rootView: view)
        navigationController?.pushViewController(hostingController, animated: true)
    }

    // UIKit UIViewController를 받는 push 함수
    func push(_ viewController: UIViewController) {
        navigationController?.pushViewController(viewController, animated: true)
    }

    func pop() {
        navigationController?.popViewController(animated: true)
    }
}

이제 SwiftUI에서 NavigationManager의 push 메서드를 통해 SwiftUI와 UIKit 스타일의 화면 전환을 모두 구현할 수 있습니다.

2. SwiftUI에서 화면 전환 제어하기

NavigationManager와 UINavigationView를 설정한 후, SwiftUI 뷰에서 UIKit 네비게이션을 통해 새로운 화면으로 전환할 수 있습니다.

RootView와 NextSwiftUIView 구현

RootView에서는 버튼을 통해 새로운 화면으로 전환하며, SwiftUI 뷰를 위한 push 메서드와 UIKit UIViewController를 위한 push 메서드를 모두 사용할 수 있습니다.

struct RootView: View {
    var body: some View {
        VStack {
            Text("Root View")

            // SwiftUI View를 push
            Button(action: {
                NavigationManager.shared.push(NextSwiftUIView())
            }) {
                Text("Go to Next SwiftUI View")
            }

            // UIKit ViewController를 push
            Button(action: {
                let nextViewController = UIViewController()
                nextViewController.view.backgroundColor = .systemBlue
                NavigationManager.shared.push(nextViewController)
            }) {
                Text("Go to Next UIKit ViewController")
            }
        }
    }
}

struct NextSwiftUIView: View {
    var body: some View {
        VStack {
            Text("Next SwiftUI View")

            Button(action: {
                NavigationManager.shared.pop()
            }) {
                Text("Back")
            }
        }
    }
}

ContentView에서 UINavigationView 사용하기

SwiftUI의 ContentView에서는 UINavigationView를 호출하고, NavigationManager를 환경 객체로 전달합니다.

struct ContentView: View {
    @StateObject private var navigationManager = NavigationManager()

    var body: some View {
        UINavigationView()
            .edgesIgnoringSafeArea(.all)
    }
}

이제 SwiftUI의 버튼에서 UIKit의 UINavigationController를 통해 화면 전환을 쉽게 할 수 있습니다.


3. 최상단에 있는 ViewController를 찾아 모달로 화면 띄우기

SwiftUI 프로젝트에서 특정 상황에 따라 최상단에 있는 뷰 컨트롤러를 탐색해 새로운 화면을 모달로 띄워야 할 때가 있습니다. 이를 위해 UIApplication의 확장으로 최상단 뷰 컨트롤러를 탐색하는 코드를 작성할 수 있습니다.

최상단 ViewController 찾기 (findRootViewController)

아래 확장 코드는 UIApplication의 루트 뷰 컨트롤러부터 시작해 현재 최상단에 있는 UIViewController를 찾아 반환합니다.

extension UIApplication {
    class func findRootViewController(base: UIViewController? = UIApplication.shared.connectedScenes
                                        .compactMap { ($0 as? UIWindowScene)?.keyWindow }
                                        .first?.rootViewController) -> UIViewController? {
        if let nav = base as? UINavigationController {
            return findRootViewController(base: nav.visibleViewController)
        }

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

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

        return base
    }
}

최상단 ViewController에서 새로운 화면 띄우기

SwiftUI 뷰에서 이 함수를 통해 현재 최상단에 있는 뷰 컨트롤러를 찾고, 그 위에 새로운 뷰를 띄울 수 있습니다.

import SwiftUI

func presentNewView() {
    if let topController = UIApplication.findRootViewController() {
        let newSwiftUIView = UIHostingController(rootView: NewSwiftUIView())
        newSwiftUIView.modalPresentationStyle = .fullScreen // 스타일 설정 가능
        topController.present(newSwiftUIView, animated: true, completion: nil)
    }
}

struct NewSwiftUIView: View {
    var body: some View {
        VStack {
            Text("New View")

            Button("Dismiss") {
                UIApplication.findRootViewController()?.dismiss(animated: true, completion: nil)
            }
        }
    }
}

이제 presentNewView() 함수를 호출하면 최상단의 뷰 컨트롤러에 NewSwiftUIView가 모달로 표시됩니다.


마무리

SwiftUI와 UIKit을 함께 사용하여 화면 전환을 구현하는 방법을 살펴보았습니다. SwiftUI의 기본 네비게이션 기능에 제한을 느끼신다면, UIKit의 UINavigationController와 같은 기존의 네비게이션 도구를 활용하는 것도 좋은 해결책이 될 수 있습니다. 이와 같은 방법을 통해 SwiftUI의 선언적 UI와 UIKit의 강력한 네비게이션 기능을 조합하여 유연한 화면 전환을 구현할 수 있습니다.

저작자표시 (새창열림)

'iOS > SwiftUI' 카테고리의 다른 글

SwiftUI에서 Combine을 활용해서 화면 회전 감지하기  (1) 2024.11.10
SwiftUI에서 각 탭마다 독립적인 네비게이션 스택 유지하기  (2) 2024.11.08
SwiftUI에서 RestAPI를 통해 서버의 데이터를 화면에 보여주기  (4) 2024.11.07
'iOS/SwiftUI' 카테고리의 다른 글
  • SwiftUI에서 Combine을 활용해서 화면 회전 감지하기
  • SwiftUI에서 각 탭마다 독립적인 네비게이션 스택 유지하기
  • SwiftUI에서 RestAPI를 통해 서버의 데이터를 화면에 보여주기
낙타가낙타낙타
낙타가낙타낙타
투덜 댈 시간에 해라
  • 낙타가낙타낙타
    노 투덜 킾 고잉
    낙타가낙타낙타
  • 전체
    오늘
    어제
    • 분류 전체보기 (10)
      • iOS (8)
        • SwiftUI (4)
        • Library (1)
      • Mac (1)
      • 개발 (0)
      • APP (0)
        • 나의 매일 (0)
        • 구디구내 (0)
        • 드립노트 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    Fastlane
    UnauthorizedAccess
    ios
    CI/CD
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
낙타가낙타낙타
SwiftUI에서 UIKit의 `UINavigationController`로 화면 전환 구현하기
상단으로

티스토리툴바