Alex Alex - 1 month ago 23
Swift Question

How to subclass custom UIViewController in Swift?

I'd like to create a reusable view controller

UsersViewControllerBase
.

UsersViewControllerBase
extends
UIViewController
, and implements two delegates (
UITableViewDelegate
,
UITableViewDataSource
), and has two views (
UITableView
,
UISegmentedControl
)

The goal is to inherit the implementation of the
UsersViewControllerBase
and customise the segmented items of segmented control in
UsersViewController
class.

class UsersViewControllerBase: UIViewController, UITableViewDelegate, UITableViewDataSource{
@IBOutlet weak var segmentedControl: UISegmentedControl!
@IBOutlet weak var tableView: UITableView!
//implementation of delegates
}

class UsersViewController: UsersViewControllerBase {
}


The
UsersViewControllerBase
is present in the storyboard and all outlets are connected, the identifier is specified.

The question is how can I init the UsersViewController to inherit all the views and functionality of
UsersViewControllerBase


When I create the instance of
UsersViewControllerBase
everything works

let usersViewControllerBase = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("UsersViewControllerBase") as? UsersViewControllerBase


But when I create the instance of
UsersViewController
I get
nil
outlets
(I created a simple
UIViewController
and assigned the UsersViewController class to it in the storyboard )

let usersViewController = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("UsersViewController") as? UsersViewController


It looks like views are not inherited.

I would expect init method in
UsersViewControllerBase
that gets controller with views and outlets from storyboard:

class UsersViewControllerBase: UIViewController, UITableViewDelegate, UITableViewDataSource{
@IBOutlet weak var segmentedControl: UISegmentedControl!
@IBOutlet weak var tableView: UITableView!
init(){
let usersViewControllerBase = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("UsersViewControllerBase") as? UsersViewControllerBase
self = usersViewControllerBase //but that doesn't compile
}
}


And I would init
UsersViewController
:

let usersViewController = UsersViewController()


But unfortunately that doesn't work

Rob Rob
Answer

When you instantiate a view controller via instantiateViewControllerWithIdentifier, the process is essentially as follows:

  • it finds a scene with that identifier;
  • it determines the base class for that scene; and
  • it returns an instance of that class.

And then, when you first access the view, it will:

  • create the view hierarchy as outlined in that storyboard scene; and
  • hook up the outlets.

(The process is actually more complicated than that, but I'm trying to reduce it to the key elements in this workflow.)

The implication of this workflow is that the outlets and the base class are determined by the unique storyboard identifier you pass to instantiateViewControllerWithIdentifier. So for every subclass of your base class, you need a separate storyboard scene and have hooked up the outlets to that particular subclass.

There is an approach that will accomplish what you've requested, though. Rather than using storyboard scene for the view controller, you can instead have the view controller implement loadView (not to be confused with viewDidLoad) and have it programmatically create the view hierarchy needed by the view controller class. Apple used to have a nice introduction to this process in their View Controller Programming Guide for iOS, but have since retired that discussion, but it can still be found in their legacy documentation.

Having said that, I personally would not be compelled to go back to the old world of programmatically created views unless there was a very compelling case for that. I might be more inclined to abandon the view controller subclass approach, and adopt something like a single class (which means I'm back in the world of storyboards) and then pass it some identifier that dictates the behavior I want from that particular instance of that scene. If you want to keep some OO elegance about this, you might instantiate custom classes for the data source and delegate based upon some property that you set in this view controller class.

I'd be more inclined to go down this road if you needed truly dynamic view controller behavior, rather than programmatically created view hierarchies. Or, even simpler, go ahead and adopt your original view controller subclassing approach and just accept that you'll need separate scenes in the storyboard for each subclass.