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()
}
Related
I've a custom UIView that I'm displaying inside a ScrollView. On my UIView I've added a CollectionView and created a custom .xib for it's cell.
In my UIView I've #IBOutlet weak var productShowcaseCollectionView: UICollectionView! and then:
override init(frame: CGRect) {
super.init(frame: frame)
initCollectionView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initCollectionView()
}
private func initCollectionView() {
self.productShowcaseCollectionView.register(UINib(nibName: "PrizeShowcaseCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: reuseIdentifier) //crashes on this line
productShowcaseCollectionView.dataSource = self
productShowcaseCollectionView.delegate = self
}
When I run it, the crash occurs at self.productShowcaseCollectionView.register that reads:
Fatal error: Unexpectedly found nil while implicitly unwrapping an
Optional value: file
You need to do this register within your viewDidLoad function (or any time after that).
productShowcaseCollectionView (an IBOutlet property) doesn't exist until that view is loaded.
Just call productShowcaseCollectionView.register in the viewDidLoad function of the ViewController your CollectionView is contained in.
Your IBOutlet will be nil until that time.
Your CollectionView delegate and dataSource are usually implemented by the containing ViewController and should referenced in the viewDidLoad function the same as described for the registering.
That way it become more reusable.
For some cases it is fine to use the delegate and dataSource inside the object itself, but I am not sure what exactly your use case is here.
I would not recomend calling the register function inside the CollectionView anyway because that way it can't be reused with other cells.
I know there are many related questions why awakeFromNib is not called when instantiating some view.
The message that the certain view is awaken from Nib is sent to the view itself and this message is not delivered to the File's Owner.
I saw Why won't my awakeFromNib fire?.
So, what will happen if you create an view's instance whose File's Owner is itself in the xib file?
In other word, you have your own custom view named MyCustomView.swift and MyCustomView.xib. And in the xib file, you set the file's owner to MyCustomView.
So, when you create the instance of MyCustomView, does awakeFromNib get called?
In my case, the awakeFromNib doesn't seem to be called.
However, the view itself is really instantiated. So, to me it is strange that the awakeFromNib is not called.
Could anyone explain this thing?
FYI:
I prepared BaseCustomView.swift.
BaseCustomView has two init.
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
and
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
And commonInit() is like this.
private func commonInit() {
// load custom view's xib
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: self.className(), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
addSubview(view)
// make custom view's size the same size with itself
view.translatesAutoresizingMaskIntoConstraints = false
let bindings = ["view": view]
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view]|",
options:NSLayoutFormatOptions(rawValue: 0),
metrics:nil,
views: bindings))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view]|",
options:NSLayoutFormatOptions(rawValue: 0),
metrics:nil,
views: bindings))
}
And customView is just the class which overrides this BaseCustomView.
In addition, the customView's File's Owner is customView itself.
More EDIT:
The custom view class is like this. And actually the awakeFromNib() is not called.
final class MyCustomView: BaseCustomView {
override func awakeFromNib() {
// do something
}
}
The "File's Owner" item in a nib is a bit special: it isn't a real object in the archive, as the other items are. It's a placeholder, that's filled in with a preexisting object when the nib is instantiated.
So, the File's Owner is not really being "awoken" from the nib as the other objects are. It's created before the nib is unarchived. Therefore, it doesn't really make sense for it to receive awakeFromNib.
Building on Josh and Kazuya's comments, File's Owner in a xib is obviously located under Placeholder and it is exactly that, a placeholder for the compiler to know there's an ivar on that xib that will have an object in memory to connect to once the xib is instantiated. And actually if you leave it blank, the AppDelegate owns it. File's Owner in my opinion is really obsolete nowadays in IB.
In the inspector on the right hand side, if you set File's Owner -> Custom Class -> Class to MyCustomView but don't set Custom Class -> Class of the xib's top-level hierarchy UIView to MyCustomView, then IBOutlet won't be connected and awakeFromNib() won't be called.
Image attached shows where MyCustomView MUST be set in order for any IBOutlets and awakeFromNib() as well as any other methods in MyCustomView.swift to be called
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.
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.