How to create view from its parent's nib using Swift - ios

I thought this question is very simple but after long search without find an answer I'm trying to ask here:
I have a custom view (MyCustomView:UIView) with a .xib file.
To use this class with its nib I add a class function as following
class func addCustomViewToView(view: UIView)
{
let CV = NSBundle(forClass: MyCustomView.self).loadNibNamed("MyCustomView", owner: self, options: nil).first as! MyCustomView
CV.frame = CGRectMake(0, 0, view.frame.width, view.frame.height)
view.addSubview(CV)
}
In another place in my application I had to extend my custom view so I created a subClass of my custom view:
class MyExtendedCustomClass : MyCustomClass
{
...
}
I tried to override the class function, but I didn't find way to create a sub class item using parent's nib file.
What is the correct way to do this?
thanks

Despite a long time past from I asked the question, I'll add my solution hope it will help to someone.
The main issue was that while I called the function
addCustomViewToView(view: UIView)
in the subCustomView it created the view as the superCustomView class due to the line:
let CV = NSBundle...loadNibNamed(...).first as! MyCustomView
so...
Firstly, the way I init my CustomView:
var view:UIViwe!
override public init(frame: CGRect)
{
super.init(frame: frame)
self.commonInit()
}
required public init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit()
{
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "CustomView", bundle: myBundle)
self.view = nib.instantiateWithOwner(self, options: nil).first as! UIView
self.view.frame = self.bounds
self.view.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
//here you can add things to your view....
self.addSubview(self.view)
}
override public class func layerClass() -> AnyClass
{
return AVPlayerLayer.self
}
Now I can initiate this class from the code using the
init(frame: CGRect)
and from storyboard by create view and define it's class to be my CustomView in that case the next init will be called:
init?(coder aDecoder: NSCoder)
After that, in the inherited View I shouldn't do nothing, it's "init" will call the "super.init" from the CustomView class and will init the nib file with no problems.

Related

Instantiating custom xib programmatically ends with infinite loop

I request apologies in advance in the case this question is very elemental or the answer is obvious.
I have a custom xib, that works very well when used with the storyboard interface builder. The custom xib is implemented like the classical samples you can find across the internet:
I have a CustomView.swift class
a CustomView.xib file.
The FileOwner of the CustomView.xib file is set to the CustomView.class. Then this xib file has a couple of outlets for the views used in the xib. Something like the following:
#IBDesignable
class CustomView: UIView {
#IBOutlet weak var view1: UIView!
#IBOutlet weak var view2: UIView!
required init?(coder aDecoder: NSCoder) {
//When loaded from storyboard.
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
//When loaded from code
super.init(frame: frame)
commonInit()
}
func commonInit() {
if let view = Bundle.main.loadNibNamed(self.nibName, owner: self, options: nil)?.first as? UIView {
view.frame = self.bounds
self.addSubview(view)
view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
}
}
func renderViews()
//A lot of stuff is done here.
}
}
As said, this works very well when using the Storyboard designer to insert the custom xib in the layout of an UIController. But I need to use the same xib on another place, and I need to instantiate and insert it programmatically in a container view multiple times.
I tried different approaches to instantiate the xib and add it as a subview of another view, for example:
class AnotherView: UIView
(...)
func instantiateXib(){
let view = CustomView()
self.addSubView(view)
}
}
or
class AnotherView: UIView
(...)
func instantiateXib(){
let nib = UINib(nibName: "CustomView", bundle: nil).instantiate(withOwner: nil, options: nil)
let view = nib.first as! CustomView
self.addSubView(view)
}
}
or many other ways that I found across the internet, but all of them end with an infinite loop, because the method init?(coder aDecoder: NSCoder) of the xib, calls the commonInit method that instantiates again an instance of the xib, and so on.
I suspect the solution is obvious, but I'm struggling to find it.
May you point me in the right direction? Thank you in advance!

Is there a more optimised/ efficient way to create custom UIView directly from XIB, in storyboard?

Assume I have a custom UIView class ColorWheelView.swift and XIB ColorWheelView.xib.
To create an custom UIView from XIB via code, here's the common practice
Via Code
extension UIView {
static func instanceFromNib() -> Self {
return getUINib().instantiate(withOwner: self, options: nil)[0] as! Self
}
static func getUINib() -> UINib {
return UINib(nibName: String(describing: self), bundle: nil)
}
}
// Create ColorWheelView from XIB.
let colorWheelView = ColorWheelView.instanceFromNib()
Via Storyboard (Doesn't look like a right way)
But, how about Storyboard? What if I use ColorWheelView as a subview in Storyboard? How can I inform Storyboard that ColorWheelView should be constructed directly from ColorWheelView.xib?
A common way I have seen so far is discussed in https://stackoverflow.com/a/34524346/72437 and https://stackoverflow.com/a/34524583/72437
import UIKit
class ColorWheelView: UIView {
let nibName = "ColorWheelView"
var contentView: UIView?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() {
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
self.addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
But, such code just doesn't look right to me. It merely
Storyboard creates a "parent" ColorWheelView without using XIB.
Then, "parent" ColorWheelView creates another "child" ColorWheelView from XIB, and used it as subview of itself.
Doesn't seem like an optimised way, as now we are having 2 instances of ColorWheelView.
Is there a better way, to tell Storyboard that I want to create a custom subview from an XIB?
import UIKit
#IBDesignable class ColorWheelView: UIView {
let nibName = "ColorWheelView"
var contentView: UIView?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() {
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
self.addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
By putting designable keyword before class name will make this view to be used in storyboard way you want(by putting custom view class name in identity inspector.

CustomView with XIB - indefinite looping in init coder method

In my project I am trying to create a custom UIView from a XIB file. I followed few tutorials and arrived at below code to load
import UIKit
class StorePricing: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupView()
}
private func setupView() {
let view = self.loadViewFromXib()
view.frame = self.bounds
view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
self.addSubview(view)
}
private func loadViewFromXib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
return view
}
}
When I add this custom view in another view my app crashed and I noticed the init call is called in a indefinite loop. The call hierarchy is as follows in my custom view
Call to init coder
Call to setupView()
Call to loadViewFromXib()
nib.instantiate calls init coder and the loop becomes indefinite
Any suggestions on how to solve this issue?
if your xib file contains an instance of your view, then then it's loaded, it will call init(coder:), which will then load the xib file again, and the cycle will restart. I would either remove instances of your view from the xib file, or don't call setupView() from within init(coder:)
Your setupView() (executed in init(coder:)) loads the nib again which fires init with coder again causing infinite recursion.
Do not instantiate the nib inside of the init(coder:). If you want to configure the view after loading it do it for example in awakeFromNib method.
You are probably getting the infinite recursion if, in your .xib file, for your new view (StorePricing) your Custom Class class type is set to your new custom class name (StorePricing). It should be set to UIView. To understand what' going on, when nib.instantiate(...) is reading the .xib and comes across the Custom Class name for your view, it calls required init?(coder aDecoder: NSCoder) to create it, and then, around it goes.
Set the File’s Owner Custom Class to your custom class. This will allow you to establish Referencing Outlets from the .xib file into your code file.
Set the File’s Owner Class to your custom class.
This is because every time you when you call setupView() you add sub view. so it happens again every time. for that what i had done you can see below. Hope will help you.
class TableBackGroundView: UIView {
// Here is my common view handled
var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func xibSetup() {
let nib = UINib(nibName: "TableBackGroundView", bundle: nil)
view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
// use bounds not frame or it'll be offset
view.frame = bounds
// Make the view stretch with containing view
view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
addSubview(view)
}
}

How to load custom UIView from xib using Swift 2?

The task is to create custom UIView that loads UI from .xib file using Swift. How do I do that?
I tried to do that using the code:
import UIKit
#IBDesignable class CustomView: UIView {
var view: UIView!
#IBOutlet weak var label: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func xibSetup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "CustomView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
It runs, but crashes with EXC_BAD_ACCESS and shows me the message:
warning: could not load any Objective-C class information. This will significantly reduce the quality of type information available.
Actually, I need to translate code given here http://qnoid.com/2013/03/20/How-to-implement-a-reusable-UIView.html to Swift 2.0, but I have no idea how to do this.
Try
let yourView = NSBundle.mainBundle().loadNibNamed("youViewNibName", owner: self, options: nil).first as! YourView
Do it in your ViewController class, then you can access the view in required init with coder, where you should call xibSetup().
Is it correct, that your class is called CustomView and you're loading CustomView.xib where the class for the view in xib is CustomView?
If that is so you probably do not understand what you're doing

Warning on Custom UIView

I'm creating popup custom UIView. I had created custom.xib, custom.swift. In my custom.xib, my owner's object refer to custom. I had implement init func in custom.swift and loadNib with the name "custom". I got infinity call from the init func until I got this warning and breakpoint at the super.init(coder: aDecoder).
warning: could not load any Objective-C class information. This will
significantly reduce the quality of type information available.
ViewController
let customView: Custom = Custom(frame: CGRectMake(100,100,200,200))
self.view.addSubview(customView)
Custom
var view: Custom!
override init(frame: CGRect) {
super.init(frame: frame)
loadNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadNib()
}
private func loadNib() -> Custom {
return NSBundle.mainBundle().loadNibNamed("Custom", owner: self, options: nil)[0] as! Custom
}
Your loadNib() method is useless in this case. It returns instance of Custom view, but the result is never used by init methods. You can declare your loadNib() as a class method and remove init in Custom class:
class Custom: UIView {
class func loadNib() -> Custom {
return NSBundle.mainBundle().loadNibNamed("Custom", owner: self, options: nil)[0] as! Custom
}
}
Then you can use your loadNib() method to instantiate Custom view directly in ViewController and change frame in this way:
let customView = Custom.loadNib()
customView.frame = CGRectMake(100,100,200,200)
self.view.addSubview(customView)
The reason you are getting this error is because your loadNib() and init methods are causing a recursion. imnosov's answer should solve your problem.

Resources