Search code examples
swiftuiviewcontroller

Cast in Swift does not work for a generic UIViewController subclass


I have a ToastViewController subclass of UIViewController with a generic ToastContentView being itself a subclass of UIView.

class ToastViewController<ContentViewType: ToastContentView>: UIViewController {
}

class ToastContentView: UIView {
}

I'm using custom view controller transitioning, and I need to cast my presenting view controller to ToastViewController from the UIViewControllerContextTransitioning instance.

if let toastVC = context.viewController(forKey: .to) as? ToastViewController<ToastContentView> {
  Log.debug("OK")
} else {
  Log.debug("NOT OK")
}

I have some concrete subclasses like AlertViewController defined like:

final class AlertViewController: ToastViewController<AlertToastView> {
}

final class AlertToastView: ToastContentView {
}

But the previous cast check always fails (whereas I'm sure the view controller is an instance of AlertViewController).

How can I make it work? I obviously don't want to cast the presenting view controller to the concrete subclasses...

Thanks


Solution

  • Well, AlertViewController is a ToastViewController<AlertToastView>, not a ToastViewController<ToastContentView>, so you cannot cast.

    A simple way to do this is to introduce a protocol with all the things you need from ToastViewController.

    @MainActor
    protocol AnyToastViewController: UIViewController {
        // declare properties/methods you need from ToastViewController here
    }
    

    If you need something of the generic ContentViewType type, add an associated type requirement to the protocol:

    associatedtype Content: ToastContentView
    var contentView: Content { get }
    

    ToastViewController would conform to it:

    class ToastViewController<ContentViewType: ToastContentView>: UIViewController, AnyToastViewController {
    

    Then you can cast to any AnyToastViewController instead.

    if let toastVC = context.viewController(forKey: .to) as? ToastViewController<ToastContentView> {
        let content = toastVC.contentView
        // and so on...
    } else {
        Log.debug("NOT OK")
    }
    

    Note that you can only get properties of the generic ContentViewType type, or call methods that return ContentViewType. Setting properties of type ContentViewType, or calling methods that take ContentViewType as a parameter is not type safe.