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)
}
}
Related
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!
I'm building a keyboard extension.
In my program I have a view controller(1) and a view(2) class which I use for a xib file .
1)
class KeyboardViewController: UIInputViewController {
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "view1", bundle: nil)
let objects = nib.instantiate(withOwner: self, options: nil)
view = objects[0] as? UIView
}
class View1: UIView {
#IBOutlet weak var someLabel: UILabel!
#IBOutlet weak var someButton: UIButton!
}
I wanted to instantiate the view class inside of my view controller so, following apple's documentation I did this:
class KeyboardViewController: UIInputViewController {
let v1 = View1()
let v2 = View2()
}
The problem is that whenever I try to call inside of my view did load something like:
v1.someLabel.text = "something"
and I run my app, for some reasons it doesn't work and eventually crashes.
Important things: the views are connected to two different .xib files and I'm working on a custom keyboard extension.
I'm sure I'm missing something in the instantiation but I can't find out what it is, I see other developers on git hub doing exactly the same as I do but running their apps gives no problem... So what am I missing out? If you can please send me more documentation about it as well...
Edit:
In both my view classes I'm doing the following to initialize them:
class View1: UIView {
#IBOutlet weak var someLabel: UILabel!
#IBOutlet weak var someButton: UIButton!
init(label: UILabel, button: UIButton) {
self.someLabel = label
self.someButton = button
super.init(frame: CGRect(x: 0, y: 0, width: 330, height: 200))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
But then in my main view controller, when i call let v1 = View1(), it becomes:
let v1 = View1(coder: NSCoder)
And I'm having a hard time figuring out what to put in the parameter field
If it's crash, I think that your UITextField in your View1 is null. This field is init in your class or it refers to a UINib or UIStoryboard ?
To fix this, try init child elements in your custom UIView as well.
I found a solution by myself:
Go to your xib file, in the file's owner section set the custom class on View1; As per the view section, set the custom class to UIView;
In your class View1: UIView add a view outlet from your xib file ie:
#IBOutlet var view: UIView!
Paste this in your class View1: UIView
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("view1", owner: self, options: nil)
addSubview(view)
view.frame = self.bounds
view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
//Add any other setup here
}
Now create another xib file, make sure it's file's Owner is KeyboardViewController. Add an UIView to it and make sure its class is of type View1.
Now go to class KeyboardViewController: UIInputViewController and link the view of type View1 in your code. Right under it write weak var v : View1!
In viewDidLoad()you can eventually write view = View1()
You're done!
Ive been searching for a while for a simple example on how to reuse views from xib files in my storyboard but all i find was outdated or dosen't solve my problem
the situation is that I has simple:
I have a viewController in my storyboard
I dragged to it two view from the library
I had created a myCustomView.xib and myCustomView.swift file (they are empty now)
I have I button on viewController (so the tree (two view and one button) are setting together on the viewController in the storyboard)
the question is: I want one view to be loaded dynamically on app launch and the other one to be loaded on button click
an other question: how can I connect myCustomView to that viewController
thank you
I've implemented an extension for UIView:
extension UIView {
static func createInstance<T: UIView>(ofType type: T.Type) -> T {
let className = NSStringFromClass(type).components(separatedBy: ".").last
return Bundle.main.loadNibNamed(className!, owner: self, options: nil)![0] as! T
}
}
In this way, wherever you can load your custom view in this way:
func override viewDidLoad() {
super.viewDidLoad()
let customView = UIView.createInstance(ofType: CustomView.self)
self.view.addSubview(customView)
}
Add bellow code in your custom view class
class MyCustomView: UIView {
#IBOutlet var contentView: UIView! // take view outlet
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
func xibSetup() {
contentView = loadViewFromNib()
// use bounds not frame or it'll be offset
contentView!.frame = bounds
//Make the view stretch with containing view
contentView!.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
// Adding custom subview on top of our view (over any custom drawing > see note below)
addSubview(contentView!)
layoutIfNeeded()
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
print("layoutIfNeeded")
}
func loadViewFromNib() -> 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)[0] as! UIView
return view
}
}
Add this class as superclass view in storyboard.
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