SwiftUI UINavigationController 사용후기

quokka·2024년 4월 29일
0

SwiftUI

목록 보기
5/7

github repository: https://github.com/quokkaKyu/SwiftUINavigationController

SwiftUI에서 화면을 전환하고싶은데 iOS 최소지원버전 때문에 고민되는 경우가 있었습니다. NavigationView는 iOS 13.0-17.5 까지만 지원을하고, NavigationStack은 iOS 16.0부터 지원을하는데 사용자를 고려한다면 현재 최소 지원버전을 iOS 16.0부터 설정하는건 무리가 있었습니다.


NavigationStack과 NavigationView의 코드를 버전마다 분기하기 번거로워서 SwiftUI에서 UINavigationController를 사용한 경험을 공유합니다.

1. 앱의 시작점 변경하기 - AppDelegate, SceneDelegate 추가, info.plist 변경

  • info.plist에 Application Scene Manifest 추가
  • SceneDelegate.swift
import SwiftUI

final class SceneDelegate: NSObject, UIWindowSceneDelegate {

    var window: UIWindow?
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
               options connectionOptions: UIScene.ConnectionOptions) {
        // Ensure the scene is of type UIWindowScene
        guard let windowScene = scene as? UIWindowScene else { return }

        let window = UIWindow(windowScene: windowScene)
        
        window.rootViewController = AppNavigation.shared.startingViewController() // rootViewController를 설정해준다.
        
        self.window = window
        window.makeKeyAndVisible()
    }
}
  • AppDelegate.swift
import UIKit

@main
final class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions
                     launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
        return true
    }
    
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession,
                     options: UIScene.ConnectionOptions
    ) -> UISceneConfiguration {
        
        let configuration = UISceneConfiguration(
            name: nil, sessionRole: connectingSceneSession.role)
        if connectingSceneSession.role == .windowApplication {
            configuration.delegateClass = SceneDelegate.self
        }
        return configuration
    }
}

2. 화면전환할 SwiftUI View를 enum으로 정의해줍니다.

  • Router.swift
import SwiftUI

public protocol Router {
    associatedtype V: View
    
    @ViewBuilder
    func view() -> V
}
  • AppRoute.swift
enum AppRoute: Router {

    case firstScreen
    case secondScreen(number: Int)
    case thirdScreen(number: Int)

    @ViewBuilder
    func view() -> some View {
        switch self {
        case .firstScreen:
             ExampleView()
        case .secondScreen(let number):
            SecondExampleView(numb: .constant(number))
        case .thirdScreen(let number):
            ThirdExampleView(numb: .constant(number))
        }
    }
}

3. RootView 설정 및 navigate 구현

  • AppNavigation.swift
import SwiftUI

class AppNavigation {
    static var shared = AppNavigation(startingRoute: .firstView)
    let startingRoute: AppRoute

    init(navigationController: UINavigationController = .init(), startingRoute: AppRoute) {
        self.startingRoute = startingRoute
    }

    func startingViewController() -> UIViewController{
        let view = startingRoute.view()
        let navigationController: UINavigationController = .init()
        let viewWithCoordinator = view.environmentObject(navigationController)
        let viewController = UIHostingController(rootView: viewWithCoordinator)
        navigationController.setViewControllers([viewController], animated: false)
        navigationController.navigationBar.isHidden = true
        return navigationController
    }

    func navigate(_ route: AppRoute, animated: Bool = true, source: UINavigationController) {
        let view = route.view()
        let viewWithNavigator = view.environmentObject(source)
        let viewController = UIHostingController(rootView: viewWithNavigator)
        source.pushViewController(viewController, animated: animated)
    }
}
  • UINavigationController+extension.swift
import UIKit

extension UINavigationController: ObservableObject {
    func navigateTo(route: AppRoute) {
        AppNavigation.shared.navigate(route, source: self)
    }

    func pop() {
        popViewController(animated: true)
    }

    func popToRoot() {
        popToRootViewController(animated: true)
    }
}

4. 사용할 SwiftUI View에서 UINavigationController선언 및 사용

  • FirstView.swift
struct FirstView: View {
    @EnvironmentObject private var navigator: UINavigationController
    let number: Int = 1
    var body: some View {
        VStack {
            Text("\(number)")
                .font(.title)
            Spacer()
            HStack {
                Button(action: {
                    navigator.navigateTo(route: .secondView(number: 2))
                }, label: {
                    Text("Move to 2")
                })
            }
        }
        .padding()
    }
}

#Preview {
    FirstView()
}

SecondView, ThirdView는 상황에 맞게 추가해주시면 됩니다.

이렇게 해서 SwiftUI의 화면전환을 UINavigationController를 이용하여 구현해봤습니다. NavigationStack을 고민하지 않고 사용하는날이 오길 기대하며 이 포스팅을 마치겠습니다. 전체 소스 및 테스트 프로젝트를 확인하시려면 맨 위 github repository 주소를 확인해주세요! 읽어주셔서 감사합니다. 피드백은 언제든지 환영입니다! :)

Reference.

profile
iOS를 공부하는 개발자입니다~ㅎㅎ

0개의 댓글