hisaac
hisaac

Reputation: 111

How to Initialize UIViews and UIViewControllers Using Protocols in Swift

I've been experimenting lately with building iOS views using a trick I learned from René Cacheaux to easily initialize UIViewControllers from code:

class NiblessViewController: UIViewController {
    init() {
        super.init(nibName: nil, bundle: nil)
    }

    @available(*, unavailable, message: "Loading this view controller from a nib is unsupported.")
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    @available(*, unavailable, message: "Loading this view controller from a nib is unsupported.")
    required init?(coder aDecoder: NSCoder) {
        fatalError("Loading this view controller from a nib is unsupported")
    }
}

You can then inherit from NiblessViewController in your custom view controller classes without having do the initializer overriding each time:

class CustomViewController: NiblessViewController {
    // ...
}

This works great when working with a vanilla UIViewController, but I can't figure out a nice way to use it with other view controller classes (e.g. UITableViewController, UINavigationController) without creating a separate Nibless class for each view controller type (e.g. NiblessTableViewController, NiblessNavigationController), containing the exact same code.

One thing I tried was to use a protocol extension like so:

protocol Nibless {}

extension Nibless where Self: UIViewController {
    // Same initialization code as above
}

Doing it this way, I get three errors saying:

  1. 'super' cannot be used outside of class members
  2. 'required' initializer in non-class type 'NiblessViewController'
  3. initializer does not override a designated initializer from its superclass

Any ideas on a nice way to do this without having duplicate code?

Upvotes: 2

Views: 947

Answers (1)

jsorge
jsorge

Reputation: 163

I totally get why you are going after what you are (to forbid IB), but it's not possible at the moment. It's also not super practical either. Let me explain:

  • The init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) method is the class's designated initializer. You should use this whenever initializing a UIViewController and marking it unavailable isn't the proper behavior.
  • For other subclasses of UIViewController they each have their own initializers (like UITableViewController(style:)), which wouldn't be covered in your protocol.
  • You can mark methods as unavailable in protocols but only @objc protocols, and in my brief testing with a playground didn't work like I was hoping it would (that would be very cool if we could do it though).
  • However even if we were able to mark protocol items as unavailable, I don't believe we could forbid access to existing members on a class through that technique.

I think your best bet might be to make your own "base" subclasses of each UIViewController subclass that you want and expressly forbid IB there. One of the nice things about initialization in code is that you can get rid of Optional<T> properties that have to litter IB-bound view controllers.

Here's a gist that I created which has the base UIViewController subclass.

Upvotes: 3

Related Questions