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!
Related
Overview
I created a custom view class and an associated nib. When I try to load it in another view controller, it loads it but the constraints are removed.
What I found
Before marking this as a duplicate... I read plenty of questions on StackOverflow of people having the same issues but the ones in swift were either unanswered either non-working. I tried everything.
What I'm doing
I have a view controller (which happens to be a keyboardviewcntroller but on a regular one I still have the problem) with an associated nib and a custom uiView with its associated nib.
KeyboardViewController:
I associated with my Keyboard view controller a zip file. His file owner is KeyboardViewController.
In my KeyboardViewController I have the basic code you get by creating a keyboard target and then I added the custom view outlet before the viewDidLoad
#IBOutlet weak var vieww: Custom!
and inside of my viewDidLoad I loaded my nib
let nib = UINib(nibName: "nibbb", bundle: nil)
let objects = nib.instantiate(withOwner: self, options: nil)
view = objects[0] as? UIView
Custom view
My custom view has an associated nib too. Its file owner is Custom (my new class). In the nib I put a button and a label as shown in the picture below and I added some constraints.
In the Custom class, I put the following code which includes outlets and the init of the class.
#IBOutlet weak var label: UILabel!
#IBOutlet weak var button: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit(){
Bundle.main.loadNibNamed("Custom", owner: self, options: nil)
addSubview(label)
label.frame = self.bounds
label.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(button)
button.frame = self.bounds
button.autoresizingMask = [.flexibleHeight, .flexibleWidth]
}
}
This is the output i get:
and of course, it's not what I wanted.
Question
How do I load a custom UIView nib from another view controller without having my constraints being messed up?
The reason your constraints are not working as expected is because in the commonInit function sets both the label and button to be the same size as the view is.
You should declare a UIView property and then replace the content of commonInit function:
var contentView: UIView!
private func commonInit() {
if let nib = Bundle.main.loadNibNamed("Custom", owner: self, options:nil)?.first as? UIView {
contentView = nib
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.frame = bounds
addSubview(contentView)
}
}
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)
}
}
I'm using DZNEmptySet to load when there's nothing to display in a UITableView. It works fine for DZNEmptySet's methods like titleForEmptyDataSet, imageForEmptyDataSet, but not the one I want to use (which is customViewForEmptyDataSet).
When I try to load the xib into the scrollView.frame, Xcode's memory starts bloating in 30 megabyte increments and the app hangs. I know I'm at fault, but I don't know what I'm mucking up.
I've looked at many answers here and tutorials on other sites, but I can't find a solution that works for this circumstance (which I think is pretty simple). Any feedback on that front would be greatly appreciated.
Here's customViewForEmptyDataSet on MyViewController
// App hangs on this and memory bloats in 30 megabyte increments.
func customViewForEmptyDataSet(scrollView: UIScrollView!) -> UIView! {
return EmptySetView(frame: scrollView.frame)
}
Here's the class for my EmptySetView that I'm trying to initialize:
import UIKit
class EmptySetView: UIView {
var view: UIView!
// These are connected to a xib
#IBOutlet weak var backgroundImageView: UIImageView!
#IBOutlet weak var viewLabel: UILabel!
#IBOutlet weak var viewTextView: UITextView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(self.view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass:self.dynamicType)
let nib = UINib(nibName: "EmptySetView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
Update
At Matt's suggestion, I investigated recursion I was experiencing and discovered source of the problem.
You do not specify the UIView class on the View for your xib. Instead, you click the yellow cube titled File's Owner and specify the UIView there, leaving the class field empty on the View in the Document Outline panel.
After cleaning caches and rebuilding, I can get the view to load in the hierarchy with this code called on MyViewController:
func customViewForEmptyDataSet(scrollView: UIScrollView!) -> UIView! {
let emptySetView = EmptySetView(frame: scrollView.frame)
return emptySetView
}
The EmptySetView xib and MyViewController don't know about each other until the xib is loaded, so you'll need to deal with layout constraints.
My guess is that in the nib you are loading the top level view is itself an EmptySetView. This is causing a recursion. You start by instantiating the EmptySetView in code:
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
In setup(), you load the nib. But this causes an EmptySetView to be instantiated again, from the nib. This time, the other initializer is called:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
But setup() loads the nib, so we are now going around in circles, trying to nest an infinite number of nib-loaded views inside one another like matrushka dolls.
I have a nib with a label in it, which is centered vertically and horizontally using Auto Layout. I declared a subclass of UIView and loaded the nib file in it.
I then added an UIView to my main view controller and assigned the new subclass to it. The problem is that the label of the nib file is not centered to the view, it doesn't follow the AutoLayout constraints.
Download test project. Why is this happening?
Here's how you should write you custom class:
import UIKit
class CustomView: UIView {
#IBOutlet var view: UIView!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
view = NSBundle.mainBundle().loadNibNamed("CustomView", owner: self, options: nil).first as! UIView
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
view.translatesAutoresizingMaskIntoConstraints = true
view.frame = bounds
self.addSubview(self.view)
}
}
Here's a working example:
Testxibautolayout2.zip
As you are loading the xib programmatically, it takes the original frame size of xib that is designed in storyboard which is 600x600. Make an outlet property of your custom view and change its frame after you load the xib! –
In your case you have to do it like:
import UIKit
class CustomView: UIView {
#IBOutlet var testView: UIView!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
NSBundle.mainBundle().loadNibNamed("CustomView", owner: self, options: nil)
self.addSubview(self.testView)
self.testView.frame=CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)
//or
self.testView.frame=self.bounds
}
}
I get "could not load any Objective-C class information. This will significantly reduce the quality of type information available." warning in the console while initializing an instance of this class:
#IBDesignable
class SystemMessage: UIView{
#IBOutlet weak var lbl_message: UILabel!
var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup(){
view = loadViewFromNib()
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
addSubview(view)
}
func loadViewFromNib() -> UIView{
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "SystemMessage", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
Execution stops on line let view = nib.instantiateWithOwner... with "Thread 1: EXC_BAD_ACCESS(code=2...)"
What could be the possible reason behind this?
Found the solution. It all comes to understanding of how xibs work.
What I did was that I set class for both view and File's Owner and connected all the outlets from the View rather than from the File's owner.
This seems like you are going the long way round instantiating a view. You have a view of class SystemMessage which instantiates a nib of name SystemMessage and then inserts that as a view :/
The simplest way to do this is to set the root view in your Xib to be of type SystemMessage
Then you connect your outlets to the view that you just gave the right type
This means that you can lose have your code and end up with
import UIKit
#IBDesignable
class SystemMessage: UIView {
#IBOutlet weak var lbl_message: UILabel!
static func loadViewFromNib() -> SystemMessage {
return NSBundle(forClass: self).loadNibNamed("SystemMessage", owner: nil, options: nil).first as! SystemMessage
}
}
This just gives you an easy way to instantiate your view from code with SystemMessage.loadViewFromNib(). File's Owner is probably being set incorrectly in this instance