I've created a reusable xib file that contains a table and it's being loaded in a TableView.swift file like this:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
Bundle.main.loadNibNamed("TableView", owner: self, options: nil)
}
I'm only mentioning this to clarify that I am not confused about how to load the xib file
I can easily load the reusable view in my RootViewController.swift file by adding a view to the UIViewController and giving that view a custom class like this:
and then creating an outlet for that view like this:
So here is my question:
Why am I not able to simply add the view like this:
let tableViewView = TableView()
When I do, I get an error that I don't totally understand:
You need to override the frame initializer as well.
Assuming your TableView class is a UITableView subclass, it should look something like this:
class TableView: UITableView {
override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
// any additional setup code
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// any additional setup code
}
}
Because you are trying to instantiate the table view programmatically, you need to give it an initializer for the frame, not only an initializer with a coder.
Related
I have navigation controller and every VC has customTableView. In my customTableView I put UI settings inside init() method like this:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
separatorColor = UIColor.orange
}
but it doesn't work. But any other parameters of tableView like backgroundColor, sectionIndexColor and others work well. So I got problem only with separator color. Just to say, all my views and cells have clearColor.
If I put this code in view controller (that has an outlet of my customTableView) inside viewDidLoad method - then it works. What I'm trying to achieve is to have only one subclassed UITableView class with predefined parameters to use in every VC in my navigation stacks.
So, I found a way to make it work. I just override separatorColor property in my customTableView class like this:
override var separatorColor: UIColor? {
get {
return UIColor.orange
}
set {
super.separatorColor = newValue
}
}
But I still cannot realise why this property cannot be set like others inside init() method.
There are at least two initializers that may be called:
1. required init?(coder aDecoder: NSCoder)
this initializer will be called when view will be initialized from Interface Builder (.xib or .storyboard). To initialize view from IB set the class of some view or subview to the view class you are using.
2. plain init()
will be called when you will initialize your view from code. There may be variation of this initializer, like init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) for UITableViewCell and so on.
So make sure if you are using right initializer. At least you can set breakpoint to see if this init is called or not.
Hope it helps!
Just a question to feed my curiosity. Can a UIView after it's initialization with a nib, load another nib again?
For example, this is where I initialize my View with a certain nib:
private func selfInit(){
Bundle(for: type(of: self)).loadNibNamed(fileName, owner: self, options: nil)
self.addSubview(contentView)
}
override init(frame: CGRect){
super.init(frame: frame)
selfInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
selfInit()
}
can I after the initialization of the nib with Bundle(...) I change the Nib again?
No Doubt that overriding the initializer to setup the initial values for setting default values for the class variables (including the UIViews) would be a good idea, but when it comes to adding a subview I would recommend to do it in the awakeFromNib() method:
Prepares the receiver for service after it has been loaded from an
Interface Builder archive, or nib file.
The nib-loading infrastructure sends an awakeFromNib message to each
object recreated from a nib archive, but only after all the objects in
the archive have been loaded and initialized. When an object receives
an awakeFromNib message, it is guaranteed to have all its outlet and
action connections already established.
Seems to be more appropriate method to add a subview in it; For instance, imagine that subview should has constraints with other components in the main view, you would need to guarantee that all components has been established.
Thus:
override func awakeFromNib() {
super.awakeFromNib()
selfInit()
}
The code below is used to manipulate and configure IB Outlets of a subclassed UICollectionViewCell.
The IB outlets in the class, however, are not yet connected at this stage.
Other SO posts like this one suggest using awakeFromNib to manipulate the IB outlets, but in all the answers, the problem deals with a custom XIB.
However, this subclass doesn't use a custom XIB.
Is it still right to use awakeFromNib to configure the IB outlets even if no custom XIB is used?
override init(frame: CGRect) {
super.init(frame: frame)
doInit(frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
doInit(frame)
}
private func doInit(frame: CGRect) {
}
I have created a custom UIView in MySample.xib. I have added the class MyView to the File Owner of xib.
MyView.swift
class MyView: UIView {
#IBOutlet var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
NSBundle.mainBundle().loadNibNamed("MySample", owner: self, options: nil)
self.addSubview(self.view)
}
}
I am now loading this MyView from MyController file like this:
MyController.swift
class MyController: UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
view.addSubview(MyView())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now to display this view, I am using to following code from another controller's UIButton:
presentViewController(MyController(), animated: true, completion: nil)
This does display the view on screen. But the problem is, it doesn't accept any user interaction. In my custom view, I have a UITableView which does display the data but it doesn't scroll or get tapped due to lack of user interaction.
Any idea what I am doing wrong?
There are some unnecessary things in your example.
I am still not sure what are you trying to do, but if you want to add a custom view from xib to your view controller then:
Create a view in a xib file , you don't need to override init , and you can't init view from xib using the default init UIView() , so please remove init method from your MyView class.
In your xib make sure that your view that you see in the IB is of the class type you want to use (i guess MyView class).
In your view controller init the view like this:
class MyController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Get all views in the xib
let allViewsInXibArray = NSBundle.mainBundle().loadNibNamed("MySample", owner: self, options: nil)
//If you only have one view in the xib and you set it's class to MyView class
let myView = allViewsInXibArray.first as! MyView
//Set wanted position and size (frame)
myView.frame = self.view.bounds
//Add the view
self.view.addSubview(myView)
//TODO: set wanted constraints.
}
}
You don't have to re-instantiate this twice
already if you using the design pattern.
It's so simple. Just write:
class MyController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Get all views in the xib
let view = MyView()
self.view.addSubview(myView)
//TODO: set wanted constraints.
}}
And It will work.
Instead of linking xib File's Owner class to MyView, I have to change the class of root view in xib to MyView. Then based on #Oleg Sherman code, it works perfectly with small changes of adding MyView() as owner to get all it's events, otherwise it will throw an error this class is not key value coding-compliant for the key ****.:
let allViewsInXibArray = NSBundle.mainBundle().loadNibNamed("MySample", owner: MyView(), options: nil)
Using File's Owner class to MyView is only required when you have to use the xib in Storyboard.
Not sure if there is a workaround to use File's Owner class to MyView when programmatically loading xib from custom controller like in my original question.
I have a .xib file called ContentView that I want to use as the view for a class called ContentView, however I cannot seem to load it.
class ContentView: UIView {
override init() {
super.init(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
fatalError("init(coder:) has not been implemented")
}
}
I'm aware that you can load a xib using the following method but this gives a error when I do so:
var contentViewXib: NSArray = NSBundle.mainBundle().loadNibNamed("ContentView", owner: nil, options: nil)
I have also set the xibs files owner to ContentView and set its Custom Class in interface builder to the class I want to use it with.
Any help is appreciated. Thanks
If you set File's owner to ContentView, then view in your XIB is not ContentView, it is normal UIView that ContentView can retrieve.
In the init method of ContentView, call NSBundle.mainBundle().loadNibNamed() just like you wrote in your question and then type:
self.addSubview(contentViewXib[0])
You will also need to set constraints or autoresizing mask for this new view.
EDIT:
Another solution is to select view directly (not File's Owner) and change its class to ContentView.