How to check if a view controller is of type UIHostingController
Asked Answered
H

2

11

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?

Homeomorphism answered 12/3, 2021 at 19:1 Comment(3)
UIHostingController is a class with generic parameter, so that does not work.Homeomorphism
xcode gives the following error: Generic parameter 'Content' could not be inferred in cast to 'UIHostingController'Homeomorphism
Can you make catalogView a property and then check something like if navigationController.viewControllers.contains(catalogView) { ... } ? (typed in browser, not tested in Xcode).Faunia
B
6

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.

Blackett answered 16/3, 2021 at 12:8 Comment(1)
this looks like a viable solution. thanksHomeomorphism
S
14

Yes, you can't cast to generic class, but you can declare protocol and implement it only for UIHostingViewController

import UIKit
import SwiftUI

private protocol AnyUIHostingViewController: AnyObject {}
extension UIHostingController: AnyUIHostingViewController {}

extension UIViewController {
   var isHosting: Bool { self is AnyUIHostingViewController } 
}

and somewhere in your code

func someFunction(viewController: UIViewController) {
  if viewController.isHosting {
     print("That is hosting view controller")
  }
}
Staton answered 19/12, 2022 at 16:21 Comment(1)
This should definitely be an accepted answer. Thanks!Karisa
B
6

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.

Blackett answered 16/3, 2021 at 12:8 Comment(1)
this looks like a viable solution. thanksHomeomorphism

© 2022 - 2024 — McMap. All rights reserved.