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.
Related
I added a custom view, that has a button, to my viewController. I want to make an #IBAction from that button to my viewController.
I have following problems:
How do I make it show up in a storyboard? When I run the app it works fine.
How do I make an #IBAction from that button to my viewController?
class CustomCellView: UIView{
//MARK: Initializer
override func awakeFromNib() {
super.awakeFromNib()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
//MARK: Functions
func commonInit(){
let customView = Bundle.main.loadNibNamed("customCellView", owner: self, options: nil)?.first as? UIView ?? UIView()
customView.frame = self.bounds
self.addSubview(customView)
}
}
Add #objc in your viewController where you added customView.
e.g.
#objc func actionCustom()
{
print("Click Added")
}
Use
func commonInit(){
let customView = Bundle.main.loadNibNamed("customCellView", owner: self, options: nil)?.first as? UIView ?? UIView()
customView.frame = self.bounds
self.addSubview(customView)
customView.button.addTarget(self, action: #selector(actionCustom), for: .touchUpInside) //Add click code here
}
my customView is not showing in storyboard but when I run the app it's ok
This is the expected behaviour with the custom views. You won't get to see how the view looks in the xib that you use the custom view only in the xib that you defined the view.
How make #IBAction from that button to my viewController.
You can't. Custom views don't make public the subviews in interface builder. The best thing you can do is to use the delegate pattern or as Bhavesh Sarsawa pointed out doing it programatically either by adding the vc as a target or by making an IBAction in the ccustom view which calls a closure that is sent by dependency injection.
customButton.setAction { code that gets called when the button is pressed }
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)
}
}
class CustomView: UIView {
var subViewColor:UIColor
var subViewMessage:String
override init(frame:CGRect) {
super.init(frame:frame)
}
init(subViewColor:UIColor,subViewMessage:String){
self.subViewColor = subViewColor
self.subViewMessage = subViewMessage
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I have a class where I want the user to initialize a custom view either by giving properties like:
let myView = CustomLoadingView(initialize properties here)
If the user does not want to initialize their own properties, I want to initialize CustomLoadingView using default properties...
let myView = CustomLoadingView() // this should initialize using default value
However, with this, I am getting this error:
Must call a designated intializer of the superclass UIView
In init(subviewColor: UIColor, subViewMessage: String), you aren't calling the designated initializer (as the compiler points out nicely).
If you don't know what designated initializers are, they are initializers that have to be called by the subclass at some point. From the docs:
Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.
In this case, the designated initializer for UIView is init(frame: CGRect), meaning at some point, your new initializer init(subviewColor: UIColor, subViewMessage: String must call super.init(frame:).
In order to fix this, make the following changes:
init(frame: CGRect, subViewColor: UIColor, subViewMessage: String){
self.subViewColor = subViewColor
self.subViewMessage = subViewMessage
super.init(frame: frame)
}
OR you can call your other initializer in your class which ends up calling the designated initializer.
override init(frame: CGRect) {
super.init(frame: frame) // calls designated initializer
}
convenience init(frame: CGRect, subViewColor: UIColor, subViewMessage: String){
self.subViewColor = subViewColor
self.subViewMessage = subViewMessage
self.init(frame: frame) // calls the initializer above
}
As for the convenience method with simply CustomLoadingView(), you have to add another initializer for that. Add this code to your custom view:
convenience init() {
self.init(frame: DEFAULT_FRAME, subViewColor: DEFAULT_COLOR, subViewMessage: DEFAULT_MESSAGE)
}
If you want to learn more about designated and convenience initializers, read about them here and here.
You must call one of UIView's designated initializers at some point in your custom initializer, for instance: super.init(frame: frameX). Your call of super.init() does not satisfy this requirement.
Foe me it was calling a xib that doesn't exist from custom view initialiser in
Bundle.main.loadNibNamed("XibNameThatDoesntExist", owner: self, options: nil)
`
Try this:
class CustomView: UIView {
var subViewColor:UIColor
var subViewMessage:String
init(subViewColor:UIColor,subViewMessage:String){
self.subViewColor = subViewColor
self.subViewMessage = subViewMessage
let frame = self.frame
//Or you can use custom frame.
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
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.
Something strange going on with IBOutlets.
In code I've try to access to this properties, but they are nil. Code:
class CustomKeyboard: UIView {
#IBOutlet var aButt: UIButton!
#IBOutlet var oButt: UIButton!
class func keyboard() -> UIView {
let nib = UINib(nibName: "CustomKeyboard", bundle: nil)
return nib.instantiateWithOwner(self, options: nil).first as UIView
}
override init() {
super.init()
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
// MARK: - Private
private func commonInit() {
println(aButt)
// aButt is nil
aButt = self.viewWithTag(1) as UIButton
println(aButt)
// aButt is not nil
}
}
That's expected, because the IBOutlet(s) are not assigned by the time the initializer is called.
Instead of calling commonInit() in init(coder:), do that in an override of awakeFromNib as follows:
// ...
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
// ...
Assuming you tried the standard troubleshooting steps for connecting IBOutlets, try this:
Apparently, you need to disable awake from nib in certain runtime cases.
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
guard subviews.isEmpty else { return self }
return Bundle.main.loadNibNamed("MainNavbar", owner: nil, options: nil)?.first
}
Your nib may not be connected. My solution is quite simple. Somewhere in your project (I create a class called UIViewExtension.swift), add an extension of UIView with this handy connectNibUI method.
extension UIView {
func connectNibUI() {
let nib = UINib(nibName: String(describing: type(of: self)), bundle: nil).instantiate(withOwner: self, options: nil)
let nibView = nib.first as! UIView
nibView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(nibView)
//I am using SnapKit cocoapod for this method, to update constraints. You can use NSLayoutConstraints if you prefer.
nibView.snp.makeConstraints { (make) -> Void in
make.edges.equalTo(self)
}
}
}
Now you can call this method on any view, in your init method, do this:
override init(frame: CGRect) {
super.init(frame: frame)
connectNibUI()
}
Building on #ScottyBlades, I made this subclass:
class UIViewXib: UIView {
// I'm finding this necessary when I name a Xib-based UIView in IB. Otherwise, the IBOutlets are not loaded in awakeFromNib.
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
guard subviews.isEmpty else { return self }
return Bundle.main.loadNibNamed(typeName(self), owner: nil, options: nil)?.first
}
}
func typeName(_ some: Any) -> String {
return (some is Any.Type) ? "\(some)" : "\(type(of: some))"
}
There is possibility that you not mentioned the FileOwner for xib.
Mention its class in File owner not in views Identity Inspector .
And how did you initiate your view from the controlller? Like this:
var view = CustomKeyboard.keyboard()
self.view.addSubview(view)