1

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.

Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719
  • You can use a `required` initialiser rather than a static `instantiate()` method (and then override that initialiser in your subclasses), and then you can use a return type of `Self` for `make()`, see for example [this Q&A](http://stackoverflow.com/q/38621563/2976878). – Hamish Feb 04 '17 at 14:00
  • Hmm, I realize @Hamish I simplified the example code too much - it's a view controller which makes itself on the fly. Editing.. – Fattie Feb 04 '17 at 14:51
  • It looks like you basically [just want this](http://stackoverflow.com/q/33200035/2976878) without the parameters for the `instantiateFromStoryboard` method, as you're defining those yourself. – Hamish Feb 05 '17 at 14:54
  • you're right that the answer by Martin there uses such a generic, thanks as always @Hamish – Fattie Feb 05 '17 at 15:21
  • it's interesting that if you just have it return the base type - don't bother with a generic - it works perfectly at runtime (returning the actual subclass) but you'd have to cast results in code, at "editor time". example: http://stackoverflow.com/a/42053648/294884 – Fattie Feb 05 '17 at 15:24
  • Well yes, so long as you've configured your storyboard properly. The problem is that `instantiateViewController` doesn't know the dynamic type of the instance it returns until runtime – thus is returns an instance statically typed as `UIViewController`. Casting doesn't change the dynamic type of the instance – it just changes the static type, adding an assertion at runtime that the static type given is correct. – Hamish Feb 05 '17 at 15:29
  • (confusingly I pasted that comment on the wrong page!) **"The problem is that instantiateViewController doesn't know what type it returns until runtime"** Exactly. You hit the nail on the head. My approach here solves that problem. The solution is each subclass has to have the "annoying little function", so above, `override class func instantiate()->Dining { // returns a "Dining"` and the same for all others. Once you make the annoying effort to cut and paste those in for each subclass, you're set. You then "get" the real class at "edit time" without having to cast every time. Whoo – Fattie Feb 05 '17 at 15:36
  • well my young language master mechanic, you've done it again :) that being said, I'm not sure if AppzYourLife's approach isn't more nicer. – Fattie Feb 05 '17 at 15:38

2 Answers2

3

You can do something like this.

class RoomBase: RoomProtocol {
    // things common to every room go here
    required init() {}
}

You can put in RoomBase all the stuff you want the other rooms to inherit.

Next you put the make() method into a protocol extension.

protocol RoomProtocol: class {
    init()
}

extension RoomProtocol where Self: RoomBase {
    static func make() -> Self {
        let room = Self()
        // set up
        return room
    }
}

Now you can write

class Dining: RoomBase {}
class Bath: RoomBase { }

And this code will work

let dining: Dining = Dining.make()
let bath: Bath = Bath.make()
Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
  • 1
    @JoeBlow: I see your point about the `Rooms` class that in your scenario cannot be represented as a protocol. Let me think a little bit about that. Finally about the "abstract methods" I still suggest avoiding them but I respect your point of view. – Luca Angeletti Feb 04 '17 at 14:40
  • @JoeBlow Good point. Just moved that part at the end of my answer. – Luca Angeletti Feb 04 '17 at 14:46
  • @JoeBlow I updated my answer, let me know what you think – Luca Angeletti Feb 04 '17 at 14:51
2

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.

Fattie
  • 27,874
  • 70
  • 431
  • 719