3

Imagine i have a BaseViewController. Then i have 2 scenarios, New and Edit, where both shares the same UI and the most of logic. So i created class NewViewController and EditViewController, subclassing BaseViewController. The problem comes when i try to instantiate "BaseViewController" from the storyboard cause i want to specify which implementation is.

if isEdit { 
    storyboard.instantiateViewControllerWithIdentifier("baseVCIdentifier") as! EditViewController 
} else {
   storyboard.instantiateViewControllerWithIdentifier("baseVCIdentifier") as! NewViewController 
}

Then i get an error: Could not cast value of type 'Test.BaseViewController' (0x10ee5e0f0) to 'Test.EditViewController' (0x10ee5f000).

I dont wanto to have both ViewController on the storyboard since i dont want to redo the same UI 2 times.

narek.sv
  • 845
  • 5
  • 22
Godfather
  • 4,040
  • 6
  • 43
  • 70
  • When a class is loaded from a xib or storyboard, the loading mechanism creates a class of the type indicated in the file. You can't magically change the class type by casting. You should be able to assign the instantiated view controller to a variable of the subclass type, but I don't know Swift well enough to know for certain, or how to write the cast. – Avi Dec 01 '15 at 09:27
  • If you could it still wouldn't work, the cast would be a lie – Wain Dec 01 '15 at 09:30
  • @Fattie I do not know if you are correct, but I do know that commenting on a comment that is 7.5 years old has no real value. – Avi Jul 27 '23 at 08:33
  • @Fattie, the root problem here is that my comment is still 100% correct. Casting at runtime does not change the type. It's also a comment on the question, not on an answer, so it really doesn't matter if the information contained within is still accurate. If you feel the question needs a better answer, provide one. Don't incorrectly nitpick 7 year old comments. – Avi Jul 28 '23 at 13:42
  • hi @Avi , sure, I'll delete my comments and be clearer. (This comment will be helpful for anyone googling here.) Note that *the loading mechanism creates a class of the type indicated in the file* is what is *wrong*. The loading mechanism creates a class of *any type you want*, which you give in the `identifier#creator` argument. TBC what you said **was correct** last decade but **is now wrong** as the way UIKit works has totally changed regarding this. I appreciate that where you say "You can't magically change the class type by casting" that is (of course, obviously) totally correct. – Fattie Jul 29 '23 at 13:46
  • As I mention, a central problem on "stackoverflow" is that extremely old comments, answers and questions go out of date very badly. (This especially applies to the iOS and droid slices of SO, which are 99.9% about the APIs of those systems.) As far as I know pretty much everyone who's a heavy user of SO, certainly me, when they see a comment that is now out of date, just adds a comment pointing that out. – Fattie Jul 29 '23 at 13:50

3 Answers3

3

You can do this using instantiateViewController(identifier:creator:).

I assume you have the view controller in a storyboard, with identifier template. The class assigned to the view controller in the storyboard should be the superclass:

let storyboard = UIStoryboard(name: "main", bundle: nil)

let viewController = storyboard.instantiateViewController(identifier: "template") { coder in 
    // this passes us with a coder for the storyboard, we can now init the preferred subclass.

    if useSubclass {
        return SpecialViewController(coder: coder)
    } else {
        return BaseViewController(coder: coder)
    }
}

Here is the documentation

Justin Meiners
  • 10,754
  • 6
  • 50
  • 92
0

It's this simple, just change "withIdentifier" to "identifier".

let v = UIStoryboard(name: "A", bundle: nil)
    .instantiateViewController(withIdentifier: "A")
present(v)

Instead ...

let v = UIStoryboard(name: "A", bundle: nil)
    .instantiateViewController(identifier: "A") { coder in
                                      return B(coder: coder) }
present(v)

It's that easy.

(This is the same concept as @JustinMeiners answer, to which I sent the large bounty, but it is coded more simply here for clarity.)


One convenient detail:

If you do this,

let v = sb.instantiateViewController(withIdentifier: "A")
present(v)

in fact in reality you have to do this

let v = sb.instantiateViewController(withIdentifier: "A") as! A
present(v)

you have to cast it to A so that you can actually use the variable, v.yourStuff = 69 etc.

So we have all typed "as! Blah" one million times.

However when you us "identifier" rather than "withIdentifier",

let v = sb.instantiateViewController(identifier: "A") { coder in
                                            return B(coder: coder) }
present(v)

In fact it's already the correct class, you can v.yourStuffInB = 11 with no cast.

Fattie
  • 27,874
  • 70
  • 431
  • 719
-1

You can't do that. Instead of sub classing create 'interaction manager' classes, or state manager classes. The base view controller would then be provided with a manager instance as part of the segue and it would forward all UI interaction to the manager for processing. You then have a single VC in the storyboard as is required and you can supply a new or edit manager. The managers can also have specific instance variables which the view controller doesn't care about.

Wain
  • 118,658
  • 15
  • 128
  • 151
  • Note, you can actually now do this, incredibly. (I'm sure @wain knows this, it's just a 10 yr old answer.) – Fattie Aug 10 '22 at 18:29