Xcode 7 Swift 2 Impossible to Instantiate Uiviewcontroller Subclass of Generic Uitableviewcontroller

Xcode 7 Swift 2 impossible to instantiate UIViewController subclass of generic UITableViewController

Unfortunately, generic Swift classes are not visible to Objective-C code and also are not supported in Interface Builder (in storyboards and xibs). I find these two points closely related.

As a solution I would suggest you to use aggregation: do not make you view controller generic, but extract some logic to another (generic) class and use it inside your view controller.

subclass of a generic UIViewController subclass isn't available in InterfaceBuilder

Answer is here: Xcode 7 Swift 2 impossible to instantiate UIViewController subclass of generic UITableViewController

TLDR; Objective-c and InterfaceBuilder can't "see" generic swift classes.

Generic: Is there a way to make compiler check T is a particular UIView subclass?

You can improve this so the above code would fail, but it can never be certain that it has the right type.

To handle the above code, you just need to change instanceFromNib() to return Self, rather than "whatever type the caller requests."

static func instanceFromNib() -> Self {
UINib(nibName: String(describing: self), bundle: nil)
.instantiate(withOwner: self, options: nil)[0] as! Self
}

But it's still possible for that NIB to hold the wrong type, and that wouldn't be discovered until runtime because you have to load the data from the file to know.

Swift how to create a generic MVP like UIViewController

In have finally found a solution, the problem was in casting self as! View, it must be self as! P.View. And there cannot be a base protocol for view because protocols do not conform to themselves in Swift. Here is my complete code:

protocol MvpPresenter {
associatedtype View
var view: View? { get set }
var isAttached: Bool { get }

func onAttach(view: View)
func onDetach(view: View)
}

/// Default implementation for the `isAttached()` method just checks if the `view` is non nil.
extension MvpPresenter {
var isAttached: Bool { return view != nil }
}

class BaseMvpViewController<M, V, P: MvpPresenter>: UIViewController {
typealias View = V
var viewModel: M? = nil
private(set) var presenter: P!

//MARK: - Initializers

required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

override public init(nibName: String?, bundle: Bundle?) {
super.init(nibName: nibName, bundle: bundle)
}

deinit {
presenter.onDetach(view: self as! P.View)
}

//MARK: - Lifecycle

override func viewDidLoad() {
super.viewDidLoad()
presenter = createPresenter()
}

override func viewWillAppear(_ animated: Bool) {
guard let view = self as? P.View else {
preconditionFailure("MVP ViewController must implement the view protocol `\(View.self)`!")
}

super.viewWillAppear(animated)

if (!presenter.isAttached) {
presenter.onAttach(view: view)
}
}

//MARK: - MVP

/// Override and return a presenter in a subclass.
func createPresenter() -> P {
preconditionFailure("MVP method `createPresenter()` must be override in a subclass and do not call `super.createPresenter()`!")
}
}

And a sample VC:

class MyGenericViewController: BaseMvpViewController<MyModel, MyView, MyPresenter>, MyView {
...
override func createPresenter() -> MainPresenter {
return MyPresenter()
}
...
}

This VC will automatically have a viewModel property of type MyModel (could be anything e.g. struct, class, enum, etc), property presenter of type MyPresenter and this presenter will be automatically attached between viewDidLoad and viewWillAppear. One method must be overridden, the createPresenter() where you must create and return a presenter. This is called before the custom VC's viewDidLoad method. Presenter is detached in the deinit.

The last problem is that generic view controllers cannot be used in interface builder (IB), because IB talks to code via Objective-C runtime and that does not know true generics, thus does not see our generic VC. The app crashes when instantiating a generic VC from a storyboard/xib. There is a trick though that fixes this. Just load the generic VC manually into the Objective-C runtime before any instantiation from storyboard/xib. Good is in AppDelegate's init method:

init() {
...
MyGenericViewController.load()
...
}

EDIT 1:
I have found the loading of generic VC into Objective-C runtime in this SO answer https://stackoverflow.com/a/43896830/671580

EDIT 2:
Sample presenter class. The mandatory things is the typealias, the weak var view: View? and the onAttach & onDetach methods. Minimum implementation of the attach/detach methods is also provided.

class SamplePresenter: MvpPresenter {
// These two are needed!
typealias View = SampleView
weak var view: View?

private let object: SomeObject
private let dao: SomeDao

//MARK: - Initializers

/// Sample init method which accepts some parameters.
init(someObject id: String, someDao dao: SomeDao) {
guard let object = dao.getObject(id: id) else {
preconditionFailure("Object does not exist!")
}

self.object = object
self.dao = dao
}

//MARK: - MVP. Both the onAttach and onDetach must assign the self.view property!

func onAttach(view: View) {
self.view = view
}

func onDetach(view: View) {
self.view = nil
}

//MARK: - Public interface

/// Sample public method that can be called from the view (e.g. a ViewController)
/// that will load some data and tell the view to display them.
func loadData() {
guard let view = view else {
return
}

let items = dao.getItem(forObject: object)
view.showItems(items)
}

//MARK: - Private
}

Initialising UIViewController subclass error

An init method in Swift like

init(scans :[Scan]){)

has be be called with

let listScanVC = ListScansVC(scans: [scan])

in the next line you have to pass the instance (starting with a lowercase letter) rather than the type

self.displayChildContent(listScanVC)

I'd recommended to use more distinctive names to avoid that type/instance confusion.

Check if two objects implement a Swift protocol and its associated type

It was trickier than I expected (so I deleted my previous post to avoid confusion) but I believe this should work for you:

 protocol ViewModelContainerVC 
{
mutating func setModel(_ :Any)
}

protocol ViewModelContainer:class,ViewModelContainerVC
{
associatedtype ViewModelType
var viewModel: ViewModelType! { get set }
}

extension ViewModelContainer
{
mutating func setModel(_ model:Any)
{ if model is ViewModelType { viewModel = model as! ViewModelType } }
}

You can then use the ViewModelContainerVC for type casting and assignment:

 if let container = container as? ViewModelContainerVC 
{
container.setModel(model)
}

[EDIT] for future reference, here's the same thing with a Bool return for type compatibility:

 protocol ViewModelContainerVC 
{
@discardableResult mutating func setModel(_ :Any) -> Bool
}

extension ViewModelContainer
{
@discardableResult mutating func setModel(_ model:Any) -> Bool
{
if let validModel = model as? ViewModelType
{ viewModel = validModel; return true }
return false
}
}

Which will allow a combined condition :

 if var container = container as? ViewModelContainerVC,
container.setModel(model)
{ ... }

How to use single storyboard uiviewcontroller for multiple subclass

great question - but unfortunately only a lame answer. I don't believe that it is currently possible to do what you propose because there are no initializers in UIStoryboard that allow overriding the view controller associated with the storyboard as defined in the object details in the storyboard on initialization. It's at initialization that all the UI elements in the stoaryboard are linked up to their properties in the view controller.

It will by default initialize with the view controller that is specified in the storyboard definition.

If you are trying to gain reuse of UI elements you created in the storyboard, they still must be linked or associated to properties in which ever view controller is using them for them to be able to "tell" the view controller about events.

It's not that much of a big deal copying over a storyboard layout especially if you only need a similar design for 3 views, however if you do, you must make sure that all the previous associations are cleared, or it will get crashes when it tries to communicate to the previous view controller. You will be able to recognize them as KVO error messages in the log output.

A couple of approaches you could take:

  • store the UI elements in a UIView - in a xib file and instantiate it from your base class and add it as a sub view in the main view, typically self.view. Then you would simply use the storyboard layout with basically blank view controllers holding their place in the storyboard but with the correct view controller sub class assigned to them. Since they would inherit from the base, they would get that view.

  • create the layout in code and install it from your base view controller. Obviously this approach defeats the purpose of using the storyboard, but may be the way to go in your case. If you have other parts of the app that would benefit from the storyboard approach, it's ok to deviate here and there if appropriate. In this case, like above, you would just use bank view controllers with your subclass assigned and let the base view controller install the UI.

It would be nice if Apple came up with a way to do what you propose, but the issue of having the graphic elements pre-linked with the controller subclass would still be an issue.

have a great New Year!!
be well



Related Topics



Leave a reply



Submit