Initializing a xib on a UIViewController's subview - ios

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.

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!

IBoutlet Connected From SuperClass UIView Still Nil

I work with Nibs. I have two screens that will use the "same" UIView component with the same behavior. It's not the same component because in each screen i placed a UIView and made the same configuration, as show on the image.
To solve this and prevent replicate the same code in other classes i wrote one class, that is a UIView subclass, with all the functions that i need.
After that i made my custom class as superclass of these UIView components to inherit the IBOutlets and all the functions.
My custom class is not defined in a Nib, is only a .swift class.
I made all the necessary connections but at run time the IBOutlets is Nil.
The code of my custom class:
class FeelingGuideView: UIView {
#IBOutlet weak var firstScreen: UILabel!
#IBOutlet weak var secondScreen: UILabel!
#IBOutlet weak var thirdScreen: UILabel!
#IBOutlet weak var fourthScreen: UILabel!
private var labelsToManage: [UILabel] = []
private var willShow: Int!
private var didShow: Int!
override init(frame: CGRect) {
super.init(frame: frame)
self.initLabelManagement()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initLabelManagement()
}
private func initLabelManagement() {
self.initLabelVector()
self.willShow = 0
self.didShow = 0
self.setupLabelToShow(label: labelsToManage[0])
self.setupLabels()
}
private func initLabelVector() {
self.labelsToManage.append(self.firstScreen)
self.labelsToManage.append(self.secondScreen)
self.labelsToManage.append(self.thirdScreen)
self.labelsToManage.append(self.fourthScreen)
}
private func setupLabels() {
for label in labelsToManage {
label.layer.borderWidth = 2.0
label.layer.borderColor = UIColor(hex: "1A8BFB").cgColor
}
}
func willShowFeelCell(at index: Int) {
self.willShow = index
if willShow > didShow {
self.setupLabelToShow(label: labelsToManage[willShow])
}
else if willShow < didShow {
for i in didShow ... willShow + 1 {
let label = labelsToManage[i]
self.setupLabelToHide(label: label)
}
}
}
private func setupLabelToShow(label: UILabel) {
label.textColor = UIColor.white
label.backgroundColor = UIColor(hex: "1A8BFB")
}
private func setupLabelToHide(label: UILabel) {
label.textColor = UIColor(hex: "1A8BFB")
label.backgroundColor = UIColor.white
}
}
I found this question similar to mine: Custom UIView from nib inside another UIViewController's nib - IBOutlets are nil
But my UIView is not in a nib.
EDIT:
I overrided the awakeFromNib but it neither enter the method.
More explanation:
My custom class is only superClass of this component:
Which i replicate on two screens.
One screen is a UITableViewCell and the another a UIViewController.
It's all about to manage the behavior of the labels depending on the screen that is showing at the moment on the UICollectionView
When the initLabelVector() function is called at the required init?(coder aDecoder:) it arrises a unwrap error:
The error when try to open the View:
Cannot show the error with the UITableViewCell because it is called at the beginning of the app and don't appear nothing. To show the error with the screen i needed to remove the call of the UITableViewCell.
The UITableViewCell is registered first with the tableView.register(nib:) and after using the tableView.dequeueReusebleCell.
The UIViewController is called from a menu class that way:
startNavigation = UINavigationController(rootViewController: FellingScreenViewController())
appDelegate.centerContainer?.setCenterView(startNavigation, withCloseAnimation: true, completion: nil)
The problem is this code:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initLabelManagement()
}
The trouble is that init(coder:) is too soon to speak of the view's outlets, which is what initLabelManagement does; the outlets are not hooked up yet. Put this instead:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.initLabelManagement()
}
How I arrived at this answer:
As a test, I tried this:
class MyView : UIView {
#IBOutlet var mySubview : UIView!
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
print(#function, self.mySubview)
}
override func awakeFromNib() {
super.awakeFromNib()
print(#function, self.mySubview)
}
}
Here's the output:
init(coder:) nil
awakeFromNib() <UIView: 0x7fc17ef05b70 ... >
What this proves:
init(coder:) is too soon; the outlet is not hooked up yet
awakeFromNib is not too soon; the outlet is hooked up
awakeFromNib is called, despite your claim to the contrary
When the init() from my custom class is called the IBOutlets are not hooked up yet.
So, I created a reference on the parent view and called the iniLabelManagement() from the viewDidLoad() method and everything worked.
Thank you matt, for the help and patience!

Xcode - How IBOutlets and Nib files work?

I'm for the first time using nib files. I mean xib and the corresponding swift class.
Here is my swift class:
import UIKit
#IBDesignable class LittleVideoView: UIView {
var view: UIView!
var nibName: String = "LittleVideoView"
// MARK: Views
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var clicksLabel: UILabel!
#IBOutlet weak var channelNameLabel: UILabel!
#IBOutlet weak var thumbnailImageView: UIImageView!
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]
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
From the editor view I created some IBOutlets as you can see. Everything works properly.
There is just something I don't understand. I am programmatically loading the nib in this class, so why can I create IBOutlets while Xcode doesn't really knows that I will really load the correct nib file? Shortly, I don't understand how IBOutlets can work in this case. So, how will Xcode correclty link the loaded UIView in the setup() method with the IBOutlets ?
Nib files have a "File's Owner" type which the editor uses to list available outlets and actions. However when the nib is loaded at runtime that type is not enforced. Outlet's are connected using -setValue:forKey: under the assumption that the "Owner" passed to instantiateWithOwner is compatible with any outlet bindings defined in the nib.
One xib File with Multiple "File's Owner"s
If you have an IBOutlet, but not a property, is it retained or not?

Cannot instantiate UIView from nib. "warning: could not load any Objective-C class information"

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

Swift: Append Custom UIView programmatically

This is a near duplicate of this other SO question, however I'm running into some issues that it makes it appear like I'm not doing it correctly. For reference, I followed this excellent tutorial on creating re-usable views from a custom class and xib file (it's only a few minutes :) and I have no problems at all dragging that into another view onto my storyboard (as demonstrated at the end of the video)
Nevertheless for my case — I'm trying to call my custom class programmatically, and add it as a subview to one of my ScrollView instances.
class MainController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
first.directionalLockEnabled = true
first.pagingEnabled = true
var item = MyCustomView(frame: CGRectMake(0, 0, self.view.frame.width, 200))
self.scrollView.addSubview(item)
}
}
My Custom view looks like this:
import UIKit
class MyCustomView: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var dressTitle: UILabel!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSBundle.mainBundle().loadNibNamed("MyCustomView", owner: self, options: nil)
self.view.frame = bounds
self.addSubview(self.view)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
}
There is an associated .xib file with it too that has the label and image.
So my question is, my view never appears in my ScrollView. I've double checked the constraints on my scroll view... I can even append simple UIView's with obvious visible dimensions CGRectMake(0, 0, 100, 100)... and nothing ever gives. What am I missing?
With some messing around I accidentally got it to work by duplicating the loadFromNib method to a second initializer in the CustomView.swift file. As seen in the video tutorial posted in the original question it shows just one of the initializers.... but in my case I had to add extra code to the init(frame). Eg:
// once with a NSCoder
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSBundle.mainBundle().loadNibNamed("Item", owner: self, options: nil)
self.view.frame = bounds
self.addSubview(self.view)
}
// for this to work programmatically I had to do the same...
override init(frame: CGRect) {
super.init(frame: frame)
NSBundle.mainBundle().loadNibNamed("Item", owner: self, options: nil)
self.view.frame = bounds
self.addSubview(self.view)
}

Resources