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
Related
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.
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.
Question: Why is UILabel nil here (code attached) in init after creating UIView programmatically from XIB? (refer to code below) That is the line label1.text = "TBC - It was updated" throws an error
Background: I want to programmatically create multiple custom views, multiple GCDateView's in this case. I want to use a XIB file to layout the custom view with an associated class to finalise customisations programmatically too, hence here I have a GCDateView.swift and a GCDateView.xib file.
Aside: As a 2nd aside question I note the view I create within the GCDateView from the xib file can't be directly allocated to be the main view (e.g. at the end of init I can't say self = gcDateViewView). Perhaps I need a separate question for this.
From within parent controller/view:
let dv = GCDateView()
GCDateView:
import UIKit
class GCDateView : UIView {
#IBOutlet weak var label1: UILabel!
func commonInit() {
// Programmtically use XIB file
if self.subviews.count == 0 {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "GCDateView", bundle: bundle)
let gcDateViewView : UIView = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
gcDateViewView.frame = self.bounds
gcDateViewView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(gcDateViewView)
}
**label1.text = "TBC - It was updated" // ** ERROR: label1 was Nil ****
}
override init(frame: CGRect) {
NSLog("GCDateView: Init frame")
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
NSLog("GCDateView: Init decoder")
super.init(coder: aDecoder)
commonInit()
}
}
IBOutlet instances are not initialized in init?(coder aDecoder: NSCoder).
This process is done in separate step.
There is method awakeFromNib where it's guaranteed that all IBOutlet instances are initialized:
override func awakeFromNib() {
super.awakeFromNib()
label1.text = "TBC - It was updated" // Won't crash.
}
To solve your second problem (i.e. avoid adding another instance of self type as self subview) I recommend to create class method that will create new instance of GCDateViewby loading it from xib.
Here is updated code:
import UIKit
class GCDateView : UIView {
#IBOutlet weak var label1: UILabel!
class func loadFromXIB() -> GCDateView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "GCDateView", bundle: bundle)
let gcDateView = nib.instantiate(withOwner: self, options: nil)[0] as! GCDateView
gcDateView.frame = self.bounds
gcDateView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
gcDateView.label1.text = "TBC - It was updated"
return gcDateView
}
}
Usage:
let dateView = GCDateView.loadFromXIB()
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
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.