iOS Swift subclassing ViewController initialized by xib (nib) file - ios

I have one base class
MyViewController: UIViewController
initialized by MyViewController.xib with some outlets. I only have set File Owner class in MyViewController.xib to MyViewController, no any init methods in MyViewController.swift (all inherited from UIViewController), and following line works just as expected:
let vc = MyViewController()
view property is set, outlets is set.
I wish to subclass MyViewController:
SecondViewController: MyViewController
{
override init()
{
super.init()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
}
Now I expect that line
let vc = SecondViewController()
will create view controller with view and outlets inherited from MyViewController, but all outlets in vc are nil. Looks like MyViewController.xib file is now missed. What am I doing wrong?

You can't extend the xib file. The SecondViewControllershould have its own xib file and its own outlets. You may define the common UI components in the base class MyViewController and for each xib you create, link the ui components directly to the base class.
For example, if you have a common custom back button in all view controller, add the outlet definition in the base class and for each xib file add the UIButton and set its outlet to the base class.

Related

Create a UIViewController concrete class from its type

I wish to create a concrete class from UIViewController type, something like this
func create(with type : UIViewController.Type)->UIViewController{
return type.init(coder: NSCoder())!
}
Apparently, UIViewController's designated initializer is only init(coder : NSCoder). And, when I try to pass in NSCoder() (as shown in the above case), the app crashes.
Anyone knows a better solution in creating a UIViewController concrete class from its type? Or am I pass in the wrong NSCoder in this case?
Code completion does not show this option, but this compiles and runs without a problem:
func create(with type : UIViewController.Type) -> UIViewController {
return type.init()
}
If you just want to create the view controller programmatically or from a XIB then just use the base constructor.
let viewController = MyViewController()
If you have a XIB with the same file name as the class then it will load it automatically.
If you want to load it from a storyboard then you need to define an identifier for the view controller in the storyboard and then call:
let storyboard = UIStoryboard(name: "storyboardName", bundle: nil)
storyboard.instantiateViewController(withIdentifier: "myViewControllerIdentifier")
EDIT
If you want a special create function, you can actually create an extension for UIViewController like this:
extension UIViewController {
static func create() -> Self {
return self.init()
}
}
Then you can call let myViewController = MyViewController.create(). However, unless you want to do something special in that create function it's a bit unnecessary.

load xib view in storyboard. why do we have to do self.addSubview(nibView) ?

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
self.addSubview(view) // My question
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: self.className, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
i want customXibView initial with nib .look like self = loadViewFromNib(), not by self.addSubview(nibView) .
I found that many of the answers are all done self.addSubview(nibView). In fact, look our view hierarchy,we will found a extra view.
This is my view hierarchy
Is there any way to instance xib to storyboard, not by self.addSubview(nibView).
if you don't understand my question,look this Custom UIView from Xib - requirements, good practices. I hope some one help me.
There are three scenarios:
You have CustomView class with all of its UI defined
programmatically in its drawRect method.
You have CustomView class with its UI defined in a subview of storyboard scene with its class set to 'CustomView'.
You have CustomView class with its UI defined in a separate xib.
Now if you want to include your CustomView in as a subview in your viewcontroller, the method differs depending how your class is defined in the first instance. Secondly how do you want to include it in you viewcontroller.
Adding CustomView with scenario 1:
Initialise the CustomView instance in your viewDidLoad method and add it to your viewcontroller's view as a subview.
Adding CustomView with condition 2:
You don't have to do anything in this scenario. Storyboards will initizlize your class for you. You can do additional setup in init(coder) method that will be called when you view is initialized.
Adding CustomView with condition 3:
The proper way to initialise a view with an xib is to initialise it programmatically via UINib's inistantiateWithOwner method. Then add the view as a subview in the viewDidLoad method of your viewcontroller.
What you are doing (or what I can guess seeing your image hierarchy) is that you have changed the class of your UIView to your custom class. Now since you have a separate xib, you are initialising it in init(coder) method and adding it as a subview to the view that was defined in and initialised by the storyboard. Hence you end up with an extra container view.
If you don't want the extra view, add your custom view programmatically.
OR
(If possible) copy the view defined in xib and place it in the viewcontroller dock and then link the view with an outlet and add it in viewDidLoad method.
Here's the answer you've wanted all along. You can just create your CustomView class, have the master instance of it in a xib with all the subviews and outlets. Then you can apply that class to any instances in your storyboards or other xibs.
No need to fiddle with File's Owner, or connect outlets to a proxy or modify the xib in a peculiar way, or add an instance of your custom view as a subview of itself (i.e. no need for self.addSubview(view), which is your question).
Just do this:
Import BFWControls framework
Change your superclass from UIView to NibView (or from UITableViewCell to NibTableViewCell)
That's it!
It even works with IBDesignable to refer your custom view (including the subviews from the xib) at design time in the storyboard.
You can read more about it here:
https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155
And you can get the open source BFWControls framework here:
https://github.com/BareFeetWare/BFWControls
And here's a simple extract of the NibReplaceable code that drives it, in case you're curious:
https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4
Tom 👣
Its not necessary that you have to add the xib view as subview. Think about the usage, as per my understanding there are tow scenarios
Suppose you have a view called customXibView class and its xib, now in the xib select the view and and from the right side panel select [identity inspector] option and check, under [custom class] by default its class is UIView. So you can keep it as UIView and set the file owner as customXibView and load it from xib as UIView instance as you are doing now. So, now the question is if you allocate customXibView object manually or in some other xib how it will show your custom components which are in your view xib. So in that case in your customXibView's init method you have to load the UIView xib and add it to customXibViewobject's view.
Another case where you can set the xib's view [custom class] as your class customXibView in this case when you call the below method
func loadViewFromNib() -> customXibView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: self.className, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? customXibView
}
You directly get an object of customXibView with UI loaded no need to add it to any other view. But in this case if you allocate the customXibView class object your UI will not be loaded if you don't do what what you are doing now. If any doubt plz comment.

Convenience initialiser for UIViewController subclass with instance from nib?

I am trying to initialise a subclass of UIViewController called TestController. I have this swift class:
class TestController : UIViewController {
let testString : String
#IBOutlet weak var test: UITextField!
required init(withString string: String) {
self.testString = string
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I also have a nib file called TestController,
inside it is an instance of UIViewController. The file's owner is none (NSObject in grey) and the class of the UIViewController instance in designer is set in the identity inspector to TestController. There is a UITextField instance as outlet.
The problem is the controller is not initialised from the nib (I think), and the textField outlet is nil.
My goal is to allow initialization of controller's instance from nib programmatically, via the custom initialiser. What am I doing wrong?
You have two problems: First, your nib (or xib) is not created properly (you shouldn't see TestController in interface builder). The easiest way to fix this is to recreate it: New File->Cocoa Touch Class->Create Subclass of UIViewController, and don't forget to check Also Create a XIB file
The second problem is that you're not specifying what nib you want to load. You should specify it in nibName parameter of the initializer:
super.init(nibName: "NewlyCreatedXib", bundle: nil)
When you fix both issues, I believe your code will work properly.

Mock UIViewController defined in Storyboard

I have a UIViewController-class instantiated via Storyboard that contains a constant property. For testing, I want to replace/mock whatever the value of the view controller.
I can actually do this by subclassing and defining a new constant and by overriding the methods that use it. However, I do not know how to instantiate the ViewController, since it's not in the storyboard.
It's important that all views and all other functionality of the original ViewController is still present, of course. How to go about it?
If i understand you need to access a property from another ViewController outside your Storyboard without presenting it. Since you're using swift, all you need to do is instantiate the class itself i believe. For example if the ViewController that is not in the storyboard has a class named "SecondController", and the variable inside second controller is called "stringVar" then all you need to do is this:
var secondVC = SecondController()
secondVC.stringVar = "new string value"
Example:
//SecondVC
import UIKit
class SecondViewController: UIViewController {
var something:String! = "String Value";
}
//Main VC
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var secondVC = SecondViewController()
secondVC.something = "Another String Value"
println(secondVC.something)
}
}

How to connect to custom UIViewContoller with xib on Storybord

I created MyViewController.swift and MyViewController.xib files.
Then I put label on xib file.
I droped to UIViewController on the Storyboad.
Then I changed it's 'Custom Class' UIViewController to MyViewController.
But it does not appear the label which on xib.
How can I show the MyViewController with xib?
Should I write some code in MyViewController to relate with xib?
I just want to reuse components with xib in my app.
Your MyViewController.swift file should at least contain the declaration for the class as this :
import UIKit
class MyViewController: UIViewController {
}
inside your class declaration you will also need the following if you want to load your view from the xib :
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
// Custom initialization
}
Finally in order to access your label you can create an IBOutlet in your class declaration like this:
#IBOutlet weak var myLabel: UILabel?
Then you will be able to connect it using Interface Builder and access it in your code

Resources