Search code examples
genericscastingswift3protocolsassociated-types

Returning the subclass in a UIViewController static


Consider a base UIViewController class...

class Rooms: UIViewController {
    class func instantiate()->Rooms {
    }

    static func make()->Rooms {
        let emplacedAndSetup = self.instantiate()
        // various kodes here
        // very likely put s.view somewhere
        return emplacedAndSetup
    }

    sundryOtherFunctionality()
}

(Note the self. before instantiate() which seems to be necessary to get "that" instantiator.)

Each subclass knows its own storyboard ID to how use to instantiateViewController:

class Dining: Rooms {
    override class func instantiate()->Dining { // returns a "Dining"
        let d = stbd.instantiateViewController(
            withIdentifier: "Some Specific Scene") as! Dining
        return d
    }
}
class Bath: Rooms {
    override class func instantiate()->Bath { // returns a "Bath"
        let b = stbd.instantiateViewController(
            withIdentifier: "Some Other Scene") as! Bath
        return b
    }
}

You can do this,

let d  = Dining.make()
let r  = Bath.make()

The only minor problem is, it returns the base class. BUT SEE BELOW. So in practice you have to

let d  = Dining.make() as! Dining
let r  = Bath.make() as! Bath

Is there a way to modify the static make so that indeed Dining.make() would return a Dining and Bath.make() would return a Bath?

( @Hamish has pointed out that one could use an init and Self pattern, Method that returns object of type which was called from However, I think that's not possible due to the instantiateViewController. )


So. Say you have code like

let d = Dining.make(blah blah)

in fact. At run time, d does become a "Dining", not a "Room"

that's fantastic.

But. If you do this in the IDE

let d:Dining = Dining.make(blah blah)

it fails - it thinks d is going to be a Room, not a Dining.

So all your code has to look like this:

let d = Dining.make(blah blah) as! Dining

which sucks. How to fix?


Note just TBC the solution is to make the static a generic, rather as in MartinR's answer here https://stackoverflow.com/a/33200426/294884 Example code in answer below.


Solution

  • I don't like to provide my own answer, but the solution is ..

    So the problem is, at editor time

    let d = Dining.make()
    

    "doesn't work", you have to do this

    let d = Dining.make() as! Dining
    

    (It DOES work at compile time, d becomes a Dining: it doesn't "work" at editor time.)

    So the solution is

    static func make()->Rooms {
        let emplacedAndSetup = self.instantiate()
        return emplacedAndSetup
    }
    

    becomes

    static func make<T: Rooms>()->T {
        let emplacedAndSetup = self.instantiate() as! T
        return emplacedAndSetup
    }
    

    So that's it.

    Note - it's entirely possible AppzForLife's solution works and/or is better as a general purpose "UIViewController auto-instantiator", but this is the answer to the question per se.