iosswiftgenericsswiftuiuihostingcontroller

How to check if a view controller is of type UIHostingController


i have a UIHostingController that is hosting a SwiftUI view called CatalogView. when showing it, an environment object is attached, so basically from UIKit it is shown like this:

let rootCatalogView = CatalogView()

let appState = AppState.get()
let catalogView = UIHostingController(rootView: rootCatalogView.environmentObject(appState))

navigationController.pushViewController(catalogView, animated: true)

now at a later time i need to check if this UIHostingController is in the list of navigationController.viewControllers

the type(of:) is showing the following, which kind of make sense:

UIHostingController<ModifiedContent<CatalogView, _EnvironmentKeyWritingModifier<Optional<AppState>>>>

things like vc.self is UIHostingController.Type or vc.self is UIHostingController< CatalogView >.Type both return false (vc is an element of navigationController.viewControllers

the following obviously works, it returns true, but any change in the initialisation of the UIHostingController will change its type

vc.isKind(of: UIHostingController<ModifiedContent<CatalogView, _EnvironmentKeyWritingModifier<Optional<StoreManager>>>>.self)

how can i check if the view controller is of type UIHostingController? or at least how can i cast the controller to UIHostingController so that i can check its rootview?


Solution

  • Due to the generic parameter, we cannot cast the ViewController to find if it is a UIHostingController without knowing the full constraint.

    I should note that this it not an ideal fix and it is really just a work around.

    UIHostingController is a subclass of UIViewController so we could do the following.

    Create a computed property on UIViewController that returns the name of the class that is used to create UIViewController. This gives us something to search for in the list of ViewControllers contained in the UINavigationController

    extension UIViewController {
        var className: String {
            String(describing: Self.self)
        }
    }
    

    Create a few UIViewController subclasses and our UIHostingController

    class FirstViewController: UIViewController {}
    class SecondViewController: UIViewController {}
    class MyHostingController<Content>: UIHostingController<Content> where Content : View {}
    
    let first = FirstViewController()
    let second = SecondViewController()
    let hosting = UIHostingController(rootView: Text("I'm in a hosting controller"))
    let myHosting = MyHostingController(rootView: Text("My hosting vc"))
    

    We can then add these to a UINavigationController.

    let nav = UINavigationController(rootViewController: first)
    nav.pushViewController(second, animated: false)
    nav.pushViewController(hosting, animated: false)
    nav.pushViewController(myHosting, animated: false)
    

    Now that we have some ViewControllers inside our UINavigationController we can now iterate across them and find a ViewController that has a className that contains what we are looking for.

    for vc in nav.viewControllers {
        print(vc.className)
    }
    

    This would print the following to the console:

    FirstViewController

    SecondViewController

    UIHostingController<Text>

    MyHostingController<Text>

    You can then for-where to find the ViewController in the hierarchy.

    for vc in nav.viewControllers where vc.className.contains("UIHostingController") {
        // code that should run if its class is UIHostingController
        print(vc.className)
    }
    
    for vc in nav.viewControllers where vc.className.contains("MyHostingController") {
        // code that should run if its class is MyHostingController
        print(vc.className)
    }
    

    As I said above, this is not an ideal solution but it may help you until there is a a better way of casting without knowing the generic constraint.