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)
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.
I'm trying to use a xib file to create a reusable component (multiple times in the same View), and everything was fine until i wanted to update some controls from an #IBInspectable property. I found that #IBOutlet's are not set at that moment, so i did a search and found something
http://justabeech.com/2014/07/27/xcode-6-live-rendering-from-nib/
He is saving a the loaded view into proxyView so you could use it in the #IBInspectable. Unfortunately that code is a bit old and doesn't work as is. But my problem is when i try to load the nib as a class it doesn't work. It only works if i load it as UIView.
This line fails as this
return bundle.loadNibNamed("test", owner: nil, options: nil)?[0] as? ValidationTextField
It only works when is like this
return bundle.loadNibNamed("test", owner: nil, options: nil)?[0] as? UIView
I think that the problem is, the xib file's owner is marked as ValidationTextField, but the main view it's UIView. So when you load the nib it brings that UIView, that obviously has no custom properties or outlets.
Many examples about loading xib files say that the class must be in the file owner. So i don't know how to get the custom class using that.
import UIKit
#IBDesignable class ValidationTextField: UIView {
#IBOutlet var lblError: UILabel!
#IBOutlet var txtField: XTextField!
#IBOutlet var imgWarning: UIImageView!
private var proxyView: ValidationTextField?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder: NSCoder) {
super.init(coder: coder)!
}
override func awakeFromNib() {
super.awakeFromNib()
xibSetup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
xibSetup()
self.proxyView?.prepareForInterfaceBuilder()
}
func xibSetup() {
guard let view = loadNib() else { return }
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
// Saving the view in a variable
self.proxyView = view
}
func loadNib() -> ValidationTextField? {
let bundle = Bundle(for: type(of: self))
return bundle.loadNibNamed("test", owner: nil, options: nil)?[0] as? ValidationTextField
}
#IBInspectable var fontSize: CGFloat = 14.0 {
didSet {
let font = UIFont(name: "System", size: self.fontSize)
self.proxyView!.txtField.font = font
self.proxyView!.lblError.font = font
}
}
}
I don't even know if the rest will work. If what the link says it's true, getting the view after loading the nib will let me access to the outlets.
Running that code fails at this line
guard let view = loadNib() else { return }
I guess that it can't convert the UIView to the class so it returns nil, and then exits.
My goal is to have a reusable component that can be placed many times in a single controller. And be able to see its design in the storyboard.
Move xibSetup() to your initializers. awakeFromNib is called too late and it won't be called if the view is created programatically. There is no need to call it in prepareForInterfaceBuilder.
In short, this can be generalized to:
open class NibLoadableView: UIView {
public override init(frame: CGRect) {
super.init(frame: frame)
loadNibContentView()
commonInit()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
loadNibContentView()
commonInit()
}
public func commonInit() {
// to be overriden
}
}
public extension UIView {
// #objc makes it possible to override the property
#objc
var nibBundle: Bundle {
return Bundle(for: type(of: self))
}
// #objc makes it possible to override the property
#objc
var nibName: String {
return String(describing: type(of: self))
}
#discardableResult
func loadNibContentView() -> UIView? {
guard
// note that owner = self !!!
let views = nibBundle.loadNibNamed(nibName, owner: self, options: nil),
let contentView = views.first as? UIView
else {
return nil
}
addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = true
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.frame = self.bounds
return contentView
}
}
Note that the view that loads the nib must be the owner of the view.
Then your class will become:
#IBDesignable
class ValidationTextField: NibLoadableView {
#IBOutlet var lblError: UILabel!
#IBOutlet var txtField: XTextField!
#IBOutlet var imgWarning: UIImageView!
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
override func commonInit() {
super.commonInit()
updateFont()
}
#IBInspectable var fontSize: CGFloat = 14.0 {
didSet {
updateFont()
}
}
private func updateFont() {
let font = UIFont.systemFont(ofSize: fontSize)
txtField.font = font
lblError.font = font
}
}
I guess that the whole idea about a proxy object comes from misuse of the Nib owner. With a proxy object, the hierarchy would have to be something like this:
ValidationTextField
-> ValidationTextField (root view of the nib)
-> txtField, lblError, imgWarning
which does not make much sense. What we really want is:
ValidationTextField (nib owner)
-> UIView (root view of the nib)
-> txtField, lblError, imgWarning
I had created some custom UIViews and implemented in another UIView in my projects.
I'm getting a crash while calling some functions or setting values to some objects but not getting crash when setting property.
Refer below code to understand code and question properly.
class SearchProfileDetailView: UIView {
#IBOutlet weak var userInfoTable: UITableView!
var userObject: UserObject?
override init(frame:CGRect) {
super.init(frame: frame)
loadViewFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib()
}
func loadViewFromNib() {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "SearchProfileDetailView", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
view.frame = bounds
self.addSubview(view);
}
func setColor() {
self.backgroundColor = UIColor.red
}
}
I've used this SearchProfileDetailView in another UIView with the interface builder and assigned outlet refer below code.
class SearchResultView: UIView {
#IBOutlet weak var profileDetailView: SearchProfileDetailView!
var currentIndex: Int?
override init(frame:CGRect) {
super.init(frame: frame)
loadViewFromNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadViewFromNib()
}
func loadViewFromNib() {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "SearchResultView", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
view.frame = bounds
self.addSubview(view);
}
func setUserProfile(userObject: UserObject) {
profileDetailView.backgroundColor = UIColor.red // Not Crashed
profileDetailView.userObject = userObject //Crashed with error mentioned in title of question
profileDetailView.setColor() ////Crashed with error mentioned in title of question
}
}
Crashed while calling profileDetailView.setColor() and
profileDetailView.userObject = userObject But not crashed on
profileDetailView.backgroundColor = UIColor.red
Now I've used this SearchResultView into UIViewController with the interface builder.
class ExploreViewController: UIViewController {
#IBOutlet weak var searchResultView: SearchResultView!
func getResult() {
SearchAPIHandler().search(params: parameters) {[weak self] (responseData) in
DispatchQueue.main.async {
guard let strongSelf = self else {
return
}
if let users = responseData.users, !users.isEmpty {
strongSelf.searchResultView.setUserProfile(userObject: users[0])
}
}
}
}
I had tried so much and now I've got to know that problem was with my interface builder.
There was none module.
I checked Inherit Module From Target and everything works well.
Several classes in my app do the same thing and have the same instance variables:
// one of the many classes I have
// they all load nibs and update the frames of their views
class HelpView: UIView {
#IBOutlet var view: UIView!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
NSBundle.mainBundle().loadNibNamed("HelpView", owner: self, options: nil)
self.addSubview(self.view)
self.view.frame = self.bounds
}
}
I want to avoid duplicated code, so I thought about using a superclass so that all the classes inherit from it.
// my new superclass all classes will inherit from
class ReusableView: UIView {
#IBOutlet var view: UIView! // all subclasses have different views
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
// every class has a different nib name
NSBundle.mainBundle().loadNibNamed("Nib name goes here", owner: self, options: nil)
self.addSubview(self.view)
self.view.frame = self.bounds
}
}
The problem is that view is nil until the nib is loaded, so it's apparently not possible to call that superclass' method because you're passing a nil object. How can I handle this?
This will work:
class ReusableView: UIView {
func getHelperView() -> UIView! {
preconditionFailure("This method must be overridden")
}
func getNibName() -> String {
preconditionFailure("This method must be overridden")
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
// every class has a different nib name
NSBundle.mainBundle().loadNibNamed(self.getNibName(), owner: self, options: nil)
self.addSubview(self.getHelperView())
self.getHelperView().frame = self.bounds
}
}
class HelpView: ReusableView {
#IBOutlet var view: UIView!
override func getHelperView() -> UIView! {
return view;
}
override func getNibName() -> String {
return "NibName";
}
}
What is wrong with the following code, it's seems to have an endless loop. Initially comes through init:frame, to commonInit, but then the XIB loading line triggers entering again through init:coder etc.
Two areas of question:
a) How to instantiate probably to avoid this problem (i.e. want to use the XIB to layout, but then dynamically creating/position multiple of these on a parent view in code)
b) setting self.label.text is problematic as it seems self.label (a UILabel linked to the XIB) hasn't been setup at this point, hence is nil. So dynamically, when I want to create this little custom UIView via XIB, add it as a subclass, then immediately set a value of the label how do I do this?
import UIKit
class DirectionInfoView: UIView {
#IBOutlet weak var label: UILabel!
func commonInit() {
let viewName = "DirectionInfoView"
let view: DirectionInfoView = NSBundle.mainBundle().loadNibNamed(viewName, owner: self, options: nil).first as! DirectionInfoView // <== EXC_BAD_ACCESS (code=2...)
self.addSubview(view)
view.frame = self.bounds
self.label.text = "testing 123"
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
}
Usage:
let newInfoView = DirectionInfoView(frame: self.mapview.bounds)
myexitingView.addSubview(newInfoView)
This seems to work:
import UIKit
class DirectionInfoView: UIView {
#IBOutlet weak var label: UILabel!
func commonInit() {
self.layer.borderWidth = 5
self.layer.cornerRadius = 25
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
}
Usage:
let directionInfoView : DirectionInfoView = NSBundle.mainBundle().loadNibNamed("DirectionInfoView", owner: self, options: nil).first as! DirectionInfoView
mapview.addSubview(directionInfoView)
directionInfoView.label.text = "It Worked!"