How to subclass UITableViewController in Swift - ios

I want to subclass UITableViewController and be able to instantiate it by calling a default initializer with no arguments.
class TestViewController: UITableViewController {
convenience init() {
self.init(style: UITableViewStyle.Plain)
}
}
As of the Xcode 6 Beta 5, the example above no longer works.
Overriding declaration requires an 'override' keyword
Invalid redeclaration of 'init()'

NOTE This bug is fixed in iOS 9, so the entire matter will be moot at that point. The discussion below applies only to the particular system and version of Swift to which it is explicitly geared.
This is clearly a bug, but there's also a very easy solution. I'll explain the problem and then give the solution. Please note that I'm writing this for Xcode 6.3.2 and Swift 1.2; Apple has been all over the map on this since the day Swift first came out, so other versions will behave differently.
The Ground of Being
You are going to instantiate UITableViewController by hand (that is, by calling its initializer in code). And you want to subclass UITableViewController because you have instance properties you want to give it.
The Problem
So, you start out with an instance property:
class MyTableViewController: UITableViewController {
let greeting : String
}
This has no default value, so you have to write an initializer:
class MyTableViewController: UITableViewController {
let greeting : String
init(greeting:String) {
self.greeting = greeting
}
}
But that's not a legal initializer - you have to call super. Let's say your call to super is to call init(style:).
class MyTableViewController: UITableViewController {
let greeting : String
init(greeting:String) {
self.greeting = greeting
super.init(style: .Plain)
}
}
But you still can't compile, because you have a requirement to implement init(coder:). So you do:
class MyTableViewController: UITableViewController {
let greeting : String
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(greeting:String) {
self.greeting = greeting
super.init(style: .Plain)
}
}
Your code now compiles! You now happily (you think) instantiate this table view controller subclass by calling the initializer you wrote:
let tvc = MyTableViewController(greeting:"Hello there")
Everything looks merry and rosy until you run the app, at which point you crash with this message:
fatal error: use of unimplemented initializer init(nibName:bundle:)
What Causes the Crash and Why You Can't Solve It
The crash is caused by a bug in Cocoa. Unknown to you, init(style:) itself calls init(nibName:bundle:). And it calls it on self. That's you - MyTableViewController. But MyTableViewController has no implementation of init(nibName:bundle:). And does not inherit init(nibName:bundle:), either, because you already provided a designated initializer, thus cutting off inheritance.
Your only solution would be to implement init(nibName:bundle:). But you can't, because that implementation would require you to set the instance property greeting - and you don't know what to set it to.
The Simple Solution
The simple solution - almost too simple, which is why it is so difficult to think of - is: don't subclass UITableViewController. Why is this a reasonable solution? Because you never actually needed to subclass it in the first place. UITableViewController is a largely pointless class; it doesn't do anything for you that you can't do for yourself.
So, now we're going to rewrite our class as a UIViewController subclass instead. We still need a table view as our view, so we'll create it in loadView, and we'll hook it up there as well. Changes are marked as starred comments:
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { // *
let greeting : String
weak var tableView : UITableView! // *
init(greeting:String) {
self.greeting = greeting
super.init(nibName:nil, bundle:nil) // *
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() { // *
self.view = UITableView(frame: CGRectZero, style: .Plain)
self.tableView = self.view as! UITableView
self.tableView.delegate = self
self.tableView.dataSource = self
}
}
Also you'll want, of course, to add the minimal required data source methods. We now instantiate our class like this:
let tvc = MyViewController(greeting:"Hello there")
Our project compiles and runs without a hitch. Problem solved!
An Objection - Not
You might object that by not using UITableViewController we have lost the ability to get a prototype cell from the storyboard. But that is no objection, because we never had that ability in the first place. Remember, our hypothesis is that we are subclassing and calling our own subclass's initializer. If we were getting the prototype cell from the storyboard, the storyboard would be instantiating us by calling init(coder:) and the problem would never have arisen in the first place.

Xcode 6 Beta 5
It appears that you can no longer declare a no-argument convenience initializer for a UITableViewController subclass. Instead, you need to override the default initializer.
class TestViewController: UITableViewController {
override init() {
// Overriding this method prevents other initializers from being inherited.
// The super implementation calls init:nibName:bundle:
// so we need to redeclare that initializer to prevent a runtime crash.
super.init(style: UITableViewStyle.Plain)
}
// This needs to be implemented (enforced by compiler).
required init(coder aDecoder: NSCoder!) {
// Or call super implementation
fatalError("NSCoding not supported")
}
// Need this to prevent runtime error:
// fatal error: use of unimplemented initializer 'init(nibName:bundle:)'
// for class 'TestViewController'
// I made this private since users should use the no-argument constructor.
private override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
}

Props to matt for a great explanation. I've made use of both matt's and #Nick Snyder's solutions, however I ran into a case in which neither would quite work, because I needed to (1) initialize let fields, (2) use init(style: .Grouped) (without getting a runtime error), and (3) use the built-in refreshControl (from UITableViewController). My workaround was to introduce an intermediate class MyTableViewController in ObjC, then use that class as the base of my table view controllers.
MyTableViewController.h
#import <UIKit/UIKit.h>
// extend but only override 1 designated initializer
#interface MyTableViewController : UITableViewController
- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER;
#end
MyTableViewController.m:
#import "MyTableViewController.h"
// clang will warn about missing designated initializers from
// UITableViewController without the next line. In this case
// we are intentionally doing this so we disregard the warning.
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
#implementation MyTableViewController
- (instancetype)initWithStyle:(UITableViewStyle)style {
return [super initWithStyle:style];
}
#end
Add the following to Project's Bridging-Header.h
#import "MyTableViewController.h"
Then use in swift. Example: "PuppyViewController.swift":
class PuppyViewController : MyTableViewController {
let _puppyTypes : [String]
init(puppyTypes : [String]) {
_puppyTypes = puppyTypes // (1) init let field (once!)
super.init(style: .Grouped) // (2) call super with style and w/o error
self.refreshControl = MyRefreshControl() // (3) setup refresh control
}
// ... rest of implementation ...
}

I did it like this
class TestViewController: UITableViewController {
var dsc_var: UITableViewController?
override convenience init() {
self.init(style: .Plain)
self.title = "Test"
self.clearsSelectionOnViewWillAppear = true
}
}
Creating and displaying a instance of TestViewController in a UISplitViewController did work for me with this code.
Maybe this is bad practice, please tell me if it is (just started with swift).
For me there's still a problem when there are non optional variables and the solution of Nick Snyder is the only one working in this situation
There's just 1 problem:
The variables are initialized 2 times.
Example:
var dsc_statistcs_ctl: StatisticsController?
var dsrc_champions: NSMutableArray
let dsc_search_controller: UISearchController
let dsrc_search_results: NSMutableArray
override init() {
dsrc_champions = dsrg_champions!
dsc_search_controller = UISearchController(searchResultsController: nil)
dsrc_search_results = NSMutableArray.array()
super.init(style: .Plain) // -> calls init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) of this class
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
// following variables were already initialized when init() was called and now initialized again
dsrc_champions = dsrg_champions!
dsc_search_controller = UISearchController(searchResultsController: nil)
dsrc_search_results = NSMutableArray.array()
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}

matt's answer is the most complete, but if you do want to use a tableViewController in the .plain style (say for legacy reasons). Then all you need to do is call
super.init(nibName: nil, bundle: nil)
instead of
super.init(style: UITableViewStyle.Plain) or self.init(style: UITableViewStyle.Plain)

I wanted to subclass UITableViewController and add a non-optional property which requires overriding the initializer and dealing with all the problems described above.
Using a Storyboard and a segue gives you more options if you can work with an optional var rather than a non-optional let in your subclass of UITableViewController
By calling performSegueWithIdentifier and overriding prepareForSegue in your presenting view controller, you can get the instance of the UITableViewController subclass and set the optional variables before initialization is completed:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segueA"{
var viewController : ATableViewController = segue.destinationViewController as ATableViewController
viewController.someVariable = SomeInitializer()
}
if segue.identifier == "segueB"{
var viewController : BTableViewController = segue.destinationViewController as BTableViewController
viewController.someVariable = SomeInitializer()
}
}

I've noticed a similar error when using static tableview cells and you gotta implement this:
init(coder decoder: NSCoder) {
super.init(coder: decoder)
}
if you implement:
required init(coder aDecoder: NSCoder!) {
// Or call super implementation
fatalError("NSCoding not supported")
}
I was just getting a crash there... Kind of as expected. Hope this helps.

Not sure it is related to your question, but in case you want to init UITableView controller with xib, Xcode 6.3 beta 4 Release Notes provide a workaround:
In your Swift project, create a new empty iOS Objective-C file. This will trigger a sheet asking you “Would you like to configure an Objective-C bridging header.”
Tap “Yes” to create a bridging header
Inside [YOURPROJECTNAME]-Bridging-Header.h add the following code:
#import UIKit;
#interface UITableViewController() // Extend UITableViewController to work around 19775924
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER ;
#end

class ExampleViewController: UITableViewController {
private var userName: String = ""
static func create(userName: String) -> ExampleViewController {
let instance = ExampleViewController(style: UITableViewStyle.Grouped)
instance.userName = userName
return instance
}
}
let vc = ExampleViewController.create("John Doe")

Related

Using segues in storyboard make a view controller that has non optional properties?

I have a view controller which has a property.
class GameVC: UIViewController {
var game:Game?
override func viewDidLoad() {
super.viewDidLoad()
}
}
For the view controller to make any sense it has to have a game property. For this reason I want the property not to be optional (I'd also prefer not to have to unwrap it when I use it the whole way through).
I'm currently overriding prepare for segue in the VC before this one and setting the game property. This is because I want to definitely be using segues and storyboards. Is there anyway I can make this view controller have a custom init and still use segues and storyboards?
Yes. You have two choices:
Initialize in the declaration:
class GameVC: UIViewController {
let game = Game()
Implement init(coder:) or awakeFromNib:
class GameVC: UIViewController {
let game : Game
required init?(coder aDecoder: NSCoder) {
self.game = Game()
super.init(coder:aDecoder)
}
In my opinion there is no good way for dependency injection when using storyboards. I think that storyboards are against object oriented design principles. For software development in a team I wouldn't recommend to use them, except for prototyping.
Instead, I use simple xib files and try to make the screens (aka UIViewControllers) as independent as possible. I also implement navigation between screens in own wireframe classes to separate the animated navigation from the main purpose of the UIViewController (constructing/managing the view tree). Then it's possible to inject the necessary objects without completely loosing the benefits of using interface builder.
Example:
final class MyViewController: UIViewController {
convenience override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
self.init()
}
convenience required init(coder aDecoder: NSCoder) {
self.init()
}
convenience init() {
fatalError("Not supported!")
}
init(viewModel: ViewModel, wireframe: Wireframe) {
self.viewModel = viewModel
self.wireframe = wireframe
super.init(nibName: "MyViewController", bundle: Bundle(for: type(of: self)))
}
private let viewModel: ViewModel
private let wireframe: Wireframe
override func viewDidLoad() {
super.viewDidLoad()
// do something with viewModel
}
#IBAction func close() {
wireframe.close()
}
}
let wireframe: Wireframe = ConcreteWireframe()
let viewModel: ViewModel = ConcreteViewModel()
let screen: UIViewController = MyViewController(viewModel: viewModel, wireframe: wireframe)
No, Storyboard (and XIB files) use init?(coder:) to instantiate elements, you can't pass additional parameters to this init.
Common practice is using implicit unwrapped optionals:
var game:Game!
This way, you can use it like a non-optional property, and it will crash on runtime if not set before used.
If you set this property in prepare(for:sender:) it will be ready in viewDidLoad, so it can be used safely.
In my apps, I just use something like
var game = Game()
That way it will never be nil and I can set the value before I segue.

Swift 3: Initializing UITableViewController

Subclassing from UITableViewController and I am getting two errors when trying to init the TVC class:
class TVC: UITableViewController {
let vm: ViewModel
override init(style: UITableViewStyle){
super.init(style: style)
self.vm = ViewModel(tvc: self) // Error: Property `self.vm` not initialized at super.init call
}
override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!){
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
// Error: Property `self.vm` not initialized at super.init call
}
}
Error: Property self.vm not initialized at super.init call
The work around of making vm an optional (vm: ViewModel?) works, but I would like to it this way, if possible.
What am I doing wrong?
There are workarounds, namely making the view model lazy (see Alesenka's solution) or an implicitly unwrapped optional (var vm: ViewModel!) and initializing after self, but more important is figuring out your design pattern.
A view model shouldn't need to know about its controller; it just has the necessary information to populate the view, which the controller uses. Further, if you're actually holding on to the view controller after initializing the view model, they're both referencing each other, and you'll have a retain cycle. For these reasons, the best solution is to eliminate the need to pass in self to the view model.
You could make vm lazy
lazy var vm: ViewModel = {
return ViewModel(tvc: self)
}()
So you don't have to init this property in init method
Actually, you can
change
let vm: ViewModel
to
var vm: ViewModel?
the error will not be showed.

"Ambiguous reference to member 'init(...)" calling baseclass initializer

I have a baseclass:
class ViewController: UIViewController
{
init(nibName nibNameOrNil: String?)
{
super.init(nibName: nibNameOrNil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) { }
}
a subclass:
class OneViewController: ViewController
{
private var one: One
init(one: One)
{
self.one = one
super.init(nibName: "OneNib")
}
required init?(coder aDecoder: NSCoder) { }
}
and a subclass of the above subclass:
class TwoViewController: OneViewController
{
private var two: Two
init(two: Two)
{
self.two = two
super.init(nibName: "TwoNib") <== ERROR
}
required init?(coder aDecoder: NSCoder) { }
}
At the indicated line I get the error:
Ambiguous reference to member 'init(one:)'
I don't understand why the compiler can't figure out I'm referring to ViewController's init(), like it managed to do in One's init().
What am I missing and/or doing wrong?
I don't understand why the compiler can't figure out I'm referring to ViewController's init(), like it managed to do in One's init()
It is because, from inside TwoViewController, the compiler can't see ViewController's init. The rule is:
As soon as you implement a designated initializer, you block inheritance of initializers.
Therefore, OneViewController has only one initializer, namely init(one:). Therefore, that is the only super initializer that TwoViewController can call.
If you want ViewController's init(nibName:) to be present in OneViewController, you must implement it in OneViewController (even if all it does is to call super).
The chapter in the Swift manual on Designated Initializers will clarify it, but basically OneViewController has only one way to set self.one, and that's by initialising using init(one: One). You cannot bypass that, which is what you are trying to do.
If what you were trying to do could succeed, what value would two.one have in the following?
let two = Two(two: SomeTwo)
print(two.one)
Answer - undefined. That's why it isn't allowed.
Having declared One.init(one:) it is now the Designated Initializer, and there's no way to go round it.
you don't necessary pass value into constructor's ViewController . you can define public object in oneViewController and you access of outer .
class OneViewController: ViewController {
public var one: One
}
let vc = storyboard?.instantiateViewControllerWithIdentifier("OneViewController") as OneViewController
let one = One()
vc.one = one

Why must I keep declaring the same required but not implemented initializer for init(coder aDecoder) for my programatic UIViewController subclass?

Perhaps it is just me, but I find certain aspects of swift... obtuse to say the least.
I don't use Interface Builder most of the time because I like using PureLayout. So I was hoping to make a UIViewController subclass, say PureViewController, that had a handy init without parameters:
class PureViewController : UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
}
}
But this is not okay, for XCode tells me I must also implement init(coder aDecoder: NSCoder). Okay, that's fine! That's why I made this class - so I don't have to do this again for subclasses.
class PureViewController : UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Ok, now here's what I don't get.
I define a subclass, SomePureViewController : PureViewController, with an initializer init(viewModel:ICrackersViewModel)...
class SomePureViewController : PureViewController {
init(viewModel:ICrackersViewModel) {
super.init()
}
}
But it STILL wants me to define the same stupid initializer till kingdom come!
class SomePureViewController : PureViewController {
init(viewModel:ICrackersViewModel) {
super.init()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now I understand the idea - there is no init(decoder) in my subclass, even though it is defined in its parent class.
Maybe I've always dealt with this issue with UIViewController and never noticed it before.
My questions are as follows:
Is there something I am doing wrong to cause this behavior?
Is there any way outside of inheritance that I can avoid repeating myself?
Are there plans to any plans to change this?
The point is that one can initialize a possibly derived class just by knowing the base type.
Lets assume a base class
class Base {
let value: Int
required init(value: Int) {
self.value = value
}
}
and a function
func instantiateWith5(cls: Base.Type) -> Base {
return cls.init(value: 5)
}
then we can do
let object = instantiateWith5(Base.self)
Now if someone defines a derived class
class Derived: Base {
let otherValue: Int
init() {
otherValue = 1
super.init(value: 1)
}
required init(value: Int) {
fatalError("init(value:) has not been implemented")
}
}
We are at least able to call
let object2 = instantiateWith5(Derived.self)
violating LSP, but that's a problem of your code, not the language.
Swift has to guarantee that initializers leave your objects in a initialized state, even when they are derived from a base object, so I guess changing that would be a bad thing. If you like to define a UIViewController that is not deserializable and thereby violating LSP it's your decision - but don't expect the language to support you in this.
I think that this is swift issue and there is no way how to avoid this. We all hate this empty - fatal error initializer.
As the initialiser is marked with the required keyword, all subclasses must implement that initialiser and they must also specify required on their implementation so that their subclasses are also required to implement it.
Required Initializers
Write the required modifier before the
definition of a class initializer to indicate that every subclass of
the class must implement that initializer
You must also write the required modifier before every subclass implementation of a required initializer, to indicate that the initializer requirement applies to further subclasses in the chain.”
This has been part of the Swift language since 1.0 and it is unlikely to change.
The issue is actually to do with the use of the required keyword in the UIViewController class definition. In theory this could be changed, but again I think it is unlikely.

Are view controllers with nib files broken in ios 8 beta 5?

I created a test project in ios 8 beta 4 which as a main view controller and a second view controller created as a UIViewController subclass with a xib file.
I put a button on the main controller to present the second controller:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func testVCBtnTapped() {
let vc = TestVC()
presentViewController(vc, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I run the app and pressing the button presents the second controller - all is well
Moving to xcode beta 5, I run the app and when I press the button the screen goes black.
Since I know they messed with the init code, I tried putting in overrides to see it that would fix it:
class TestVC: UIViewController {
override init() {
super.init()
}
required init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
Same problem. Changing the required and overrides in to all possible combinations accepted by xcode has no effect.
If I use the storyboard to create another controller and segue to it all is well.
Any ideas?
EDIT - New info
Tried nibName = nil in init - same problem
Created the same app in objective c and it works fine
Apparently a swift beta 5 problem
I can't tell whether it's a bug or not, but it is definitely a change. Of course they might change it back... In any case, the rule in seed 5 is:
The view controller will not automatically find its .xib just because it has the same name. You have to supply the name explicitly.
So in your case any attempt to initialize this view controller must ultimately call nibName:bundle: with an explicit nib name ("TestVC"), I assume.
If you want to be able to initialize by calling
let vc = TestVC()
as you are doing in your presenting view controller, then simply override init() in the presented view controller to call super.init(nibName:"TestVC", bundle:nil) and that's all you need (except that I presume you will also need the init(coder:) stopper discussed here).
EDIT You are absolutely right that this is a Swift-only problem. Well spotted. In Objective-C, initializing with init (or new) still works fine; the view controller finds its eponymous .xib file correctly.
ANOTHER EDIT The determining factor is not whether Objective-C or Swift calls init. It is whether the view controller itself is written in Objective-C or Swift.
FINAL EDIT The workaround is to declare your Swift view controller like this:
#objc(ViewController) ViewController : UIViewController { // ...
The name in parentheses gets rid of the name mangling which is causing the problem. It is likely that in the next release this will be fixed and you can take the #objc away again.
ANOTHER FINAL EDIT Bad news: the bug report I filed on this came back "works as intended". They point out that all I have to do is name the .xib file after the surrounding module, e.g. if my app is called NibFinder, then if I name my .xib file NibFinder.ViewController.xib, it will be found automatically when instantiating ViewController().
That is true enough, but in my view it merely restates the bug; the Swift lookup procedure is prepending the module name. So Apple is saying I should knuckle under and prepend the same module name to my .xib file, whereas I am saying that Apple should knuckle under and strip the module name off as it performs the search.
EDIT THAT IS TRULY TRULY FINAL This bug is fixed in iOS 9 beta 4 and all these workarounds become unnecessary.
in case if You need support of iOS8.
You can use extension on UIViewController
extension UIViewController {
static func instanceWithDefaultNib() -> Self {
let className = NSStringFromClass(self as! AnyClass).componentsSeparatedByString(".").last
let bundle = NSBundle(forClass: self as! AnyClass)
return self.init(nibName: className, bundle: bundle)
}
}
and then, just create the instance this way
let vc = TestVC.instanceWithDefaultNib()
This trick works for me. If you have a base view controller class you can override the nibName property returning the class name (equals to the xib file name).
class BaseViewController: UIViewController {
override var nibName: String? {
get {
guard #available(iOS 9, *) else {
let nib = String(self.classForCoder)
return nib
}
return super.nibName
}
}
}
All view controllers that inherit from this base class could load their xib.
e.g. MyViewController: BaseViewController could load MyViewController.xib
I've had this problem recently, and my solution is to override the nibName method in baseviewController. My solution is as follows:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init() {
super.init(nibName: nil, bundle: Bundle.main)
}
override var nibName: String? {
get {
if #available(iOS 9, *) {
return super.nibName
} else {
let classString = String(describing: type(of: self))
guard Bundle.main.path(forResource: classString, ofType: "nib") != nil else {
return nil
}
return classString
}
}
}
Here's code, based on Francesco answer. It contains check if nib file exits, so app will not crashed when you load UIViewController which doesn't have associated xib (you can reproduce it on iOS8)
override var nibName: String? {
get {
let classString = String(describing: type(of: self))
guard nibBundle?.path(forResource: classString, ofType: "nib") != nil else {
return nil
}
return classString
}
}
override var nibBundle: Bundle? {
get {
return Bundle.main
}
}
Swift3:
extension UIViewController {
static func instanceWithDefaultNib() -> Self {
let className = NSStringFromClass(self).components(separatedBy: ".").last
return self.init(nibName: className, bundle: nil)
}
}
I had a very similar problem on Xcode 10.1 and iOS 12.
This code worked:
class ExampleViewController: UIViewController {
init() {
super.init(nibName: "ExampleViewController", bundle: nil)
}
}
But passing nil as nibName made it not load the XIB.
When specifying the class name explicitly via #objc("ExampleViewController") this fixed it.
The problem was caused by the module name starting with the letters 'UI' (the project was called UIKitExample). When I renamed the target to something else, this fixed the problem.

Resources