Nil error when presenting UIView in iOS app - ios

I am trying to create a view which gets presented from a UIViewController. To do this, I have created a subclass of a UIView (OnboardingCaptureView.swift), and a .xib interface file to go with it. In my interface, I have a label, called alertLabel. And in my init method in my OnboardingCaptureView class, I am setting 4 parameters, and setting the label to the string parameter, message.
For some reason, when I allocate this view and add it as a subview, it crashes and says "fatal error: unexpectedly found nil while unwrapping an Optional value". When I remove this label, it crashes in my required init? method. I have no idea why I can't create and present a simple subclass of the UIView. I have a feeling that Xcode is bugging out on me, but I've built, cleaned derived data, and restarted multiple times. Is there something wrong with my code?
//Custom UIView subclass
class OnboardingCaptureView: UIView {
#IBOutlet private weak var alertLabel: UILabel!
init(triangleXOffset: CGFloat, closeButtonOnTop: Bool, message: String, frame: CGRect) {
super.init(frame: frame);
//Crashes at next line, claims self.alertLabel is nil
self.alertLabel.text = message
connectNibUI()
}
required init?(coder aDecoder: NSCoder) {
//If i disconnect the alertLabel outlet and remove it, it crashes here
fatalError("init(coder:) has not been implemented")
}
}
//Creating and adding subview
let onboardingCaptureView = OnboardingCaptureView(triangleXOffset: 0, closeButtonOnTop: true, message: "Alert", frame: CGRectMake(0,0,200,200))
self.onboardingView = onboardingCaptureView
self.view.addSubview(onboardingCaptureView)
//EDIT -> connectNibUI method
extension UIView {
func connectNibUI() {
let nib = UINib(nibName: String(self.dynamicType), bundle: nil).instantiateWithOwner(self, options: nil)
let nibView = nib.first as! UIView
nibView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(nibView)
nibView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(self)
}
}
}

Seems likely your alertLabel hasn't actually been assigned.
First, just double check to make sure you actually linked the outlet to the nib.
What actually seems like the issue here is that your custom view is being created, but the nib isn't. What's going on in your connectNibUI() function? If that's where you're loading in the nib, all you may need to do is set the label text after that function.

Related

There is difference in behaviour when an instance variable is computed instead of being stored

I have a custom UIView class that is initiated from the xib file. It has instance property called title of type String?. Whenever, the title property is set, the text of a UITextField gets changed to the value of the title property.
If the title property is a stored property, the program works as expected.
If the title property is a computed property, then the program crashes with EXC_BAD_ACCESS error which I assume is because an IBOutlet had not yet been initialized.
Can anyone explain why if title is a stored property, it works but if it is an computed property it fails?
Following is the source code-
The NibView is a subclass of UIView and handles the loading of xib file
class NibView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
loadNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadNib()
}
}
The implementation of loadNib method is inside an extension
extension UIView {
func loadNib() {
guard let view = nib.instantiate(withOwner: self, options: nil).first as? UIView else { return }
view.frame = bounds
addSubview(view)
}
}
The definition of nib property on UIView is in another extension
extension UIView {
static var nib: UINib {
return UINib(nibName: String(describing: self), bundle: nil)
}
var nib: UINib {
return type(of: self).nib
}
}
The following class is the class which has the title property.
class ProgressView: NibView {
var title: String? {
didSet {
titleLabel.text = title
}
}
#IBOutlet private weak var titleLabel: UILabel!
}
The above class is used as follows-
let view = ProgressView()
addSubview(view)
view.title = "Loading"
Running the above code works as expected.
However if the implementation of ProgressView is changed to use a computed property as below, then it fails
class ProgressView: NibView {
var title: String? {
get {
return titleLabel.text
}
set {
titleLabel.text = newValue
}
}
#IBOutlet private weak var titleLabel: UILabel!
}
Can anyone point out why where is difference in behaviour when the title property is computed instead of being stored?
Edit -
The main thread crashes with "Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)"
The method on top of the call stack is "ProgressView.title.modify".
Edit 2-
I am not sure what I have done but I am unable to reproduce the issue after restarting xcode. Even if computed property is used, it works as expected.
Your description is far from explanatory, but I'm guessing that there is a ProgressView nib in which the File's Owner is a ProgressView, and there is an titleLabel outlet from the File's owner to a label inside the nib. (I assume this because otherwise I can't explain your use of withOwner: self.)
On that assumption I can't reproduce any problem. Both your ways of expressing title work just fine for me. I put print statements to make sure the right one was being called, and it is; no matter whether this is a didSet or the setter of a computed property, we load just fine and I see the "Loading" text.
My code is in a view controller's viewDidLoad, if that makes a difference.
(By the way, I regard your use of ProgressView() with suspicion. This results in a zero-size view. It might not seem to make any difference, but it's a bad idea. The label is a subview of the zero-size view. If the zero-size view clipped its subviews, the label would be invisible. Even if the zero-size view does not clip its subviews, if the label were a button, the button would not work. Zero-size views are a bad idea. You should give your ProgressView a real frame.)

Delegate is null for some reason

I have been debugging for a day and decided I have no idea what is causing the error in my app. It would be awesome if anyone could help me out figure it out.
So I created a custom UIView from a Nib File with class name ManualScreen. xibsetup() basically is in UIView extension which just loads from the Nib file. I want to send the button tap from my view to ViewController. I directly did not add this view to the ViewController because I need to remove this ManualScreen view and add another view in its place when Segment Control is moved to another option.
class ManualScreen: UIView {
var mManualViewListener:ManualViewListener!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: CGRect.zero)
xibSetup()
}
#IBOutlet weak var counterLabel: UILabel!{
didSet {
print("labelView: \(String(describing: counterLabel))")
}
}
#IBAction func addButton(_ sender: UIButton) {
if(mManualViewListener != nil){ ->>>this is always nil for some reason
print("insdie the listener counting")
mManualViewListener.addCount()
}else{
print("listener is nil")
}
}
func addListener(manualViewListener:ManualViewListener){
print("adding listener")
mManualViewListener = manualViewListener
}
}
This UIView is initilized in the Viewcontroller and this Viewcontroller also implements my delegate protocol. When I initalized my customView in the Viewcontroller, I add this Viewcontroller as the delegate by doing
var manualScreen = ManualScreen()
manualScreen.addListener(manualViewListener: self)
My delegate protocol is
protocol ManualViewListener {
func addCount()
}
Once listener is set, I should be able to send some event (here button touch) from my view to the ViewController using manualViewListener.addcount(). But it says my manualViewListener is nil always.
I have just written a small portion of my code here as writing everything will be not feasible. If anyone wants to see the whole app, here is the GitHub link to the thing I am working. https://github.com/Rikenm/Auto-Counter-iOS
It doesn't look pretty for now. I am just working on the functionality right now.
And finally thank you for the help.
Your problem is here
override init(frame: CGRect) {
super.init(frame: CGRect.zero)
xibSetup() // this is the problem maker
}
you add a new view of the same class above it and for sure it's listener object is nil with the the screen view that you instantiate here
mManualScreen = ManualScreen()
mManualScreen.addListener(manualViewListener: self)
//
extension UIView{
func xibSetup() {
let view = loadFromNib()
addSubview(view)
stretch(view: view)
}
// 2. Loads the view from the nib in the bundle
/// Method to init the view from a Nib.
///
/// - Returns: Optional UIView initialized from the Nib of the same class name.
func loadFromNib<T: UIView>() -> T {
let selfType = type(of: self)
let bundle = Bundle(for: selfType)
let nibName = String(describing: selfType)
let nib = UINib(nibName: nibName, bundle: bundle)
guard let view = nib.instantiate(withOwner: self, options: nil).first as? T else {
fatalError("Error loading nib with name \(nibName) ")
}
return view
}
}
Instead you need
var mManualViewListener:ManualViewListener!
static func loadFromNib() -> ManualScreen {
let view = Bundle.main.loadNibNamed("ManualScreen", owner: self, options: nil)?.first as! ManualScreen
return view
}
with
mManualScreen = ManualScreen.loadFromNib()
mManualScreen.addListener(manualViewListener: self)
The problem is that you're creating 2 separate ManualScreen instances. Your method xibSetup creates and returns another ManualScreen instance and adds it as a subview of your first ManualScreen, which is attached to your detail view controller. If you set a breakpoint within addManualScreen() in your DetailViewController and inspect mManualScreen's subviews, you'll see another one.
Hence, you're setting the mManualViewListener delegate property to a ManualScreen, but the extra ManualScreen (which you shouldn't be creating) added as a subview from xibSetup() is intercepting the action, and that view doesn't have an mManualViewListener attached to it.
You should fix your view instantiation to only create one instance of ManualScreen and you will fix the problem.
I tried adding a couple of breakpoints to your code. It seems the way you're adding the view is a little (a lot?) off.
Settings Breakpoints
First off, I added a breakpoint to your addManualScreen method in line 89:
containerView.addSubview(mManualScreen)
And another breakpoint in your ManualScreen itself, the function addButton, line 51:
if(mManualViewListener != nil){
First Breakpoint Hit
OK, breakpoint one hit. What is mManualScreen at this point?
po mManualScreen
gives us amongst other things the object ID Auto_Counter.ManualScreen: 0x7fcfebe018d0
is the delegate set?
po mManualScreen.mManualViewListener
indeed it is: some : <Auto_Counter.DetailViewController: 0x7fcfeb837fb0>
Second Breakpoint Hit
OK, second breakpoint hit when I tap the + button. Is the mManualListener still set?
po mManualViewListener
Nope, we get nil
Lets take a look at the object itself then
po self
gives us
Auto_Counter.ManualScreen: 0x7fcfe8d4b300
Hang on, that's not the same object ID!
The Problem
Now take a look at xibSetup
func xibSetup() {
let view = loadFromNib()
addSubview(view)
stretch(view: view)
}
Here is where your second/inner view is created! And this is the view that reacts to your #IBAction.
Solution
You should rethink how you create your manual view, I can't really come up with the correct solution as it seems a bit convoluted at the moment, but you need to use either the nib creation method...or create it manually.
Update It seems others has found the correct solution. I hope my answer helps you in how to diagnose these kinds of problems another time at least then so you can reduce the frustration period from a day to maybe just half a day :)
Hope that helps.

Cannot pass 'var' by 'init' into '#IBAction'

I created a pair of xib file with the swift file(for that xib file).
[xib_template.xib & view_template.swift]
And I want to control this pair of xib file by my [main_VC.swift].
xib file have 1 button and 1 label.
I want to change the text of label when I click this button.
I want to set different template view and control them in my [main_VC].
But the #IBAction seems independent inside the class
I pass the value from [main_VC] to [view_template.swift] by init method searched on the internet.
I can get correct value by using func in [main_VC].
But when clicking the button,
the value is always nil.
The var inside IBAction cannot get the value from init.
I am new in swift and I tried my best but still cannot fix this.
How can I get the init value inside IBAction?
Or how can I programmatically create & disable the Ibaction from [main_VC]?
I adjusted my code to be more easy to read.
May have some little typing error.
I searched online and tried all I can already.
One people asked similar question before but have no answer.
Hope for help.
Thanks very much.
[view_template.swift]
import UIKit
class View_template_empty: UIView {
var _uid: String?
#IBOutlet weak var labellabel: UILabel!
init (uid: String) {
self._uid = uid
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
}
required init?(coder aDecoder: NSCoder) {
// fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder)
}
#IBAction func clickingPanel2(_ sender: Any) {
print(self._uid) // always nil !!!!!!
self.labellabel.text = “test”
}
fun test () {
print(self._uid) // correct value
}
}
[main_VC] (only copy out the main function)
func allocator (_uid: String, uiView: UIView) {
switch templateType {
case “one”:
if let loading_panels = Bundle.main.loadNibNamed("xib_template", owner: uiView, options: nil)?.first as? view_template {
loading_panels.translatesAutoresizingMaskIntoConstraints = false
uiView.addSubview(loading_panels)
loading_panels.leadingAnchor.constraint(equalTo: uiView.leadingAnchor).isActive = true
loading_panels.trailingAnchor.constraint(equalTo: uiView.trailingAnchor).isActive = true
loading_panels.bottomAnchor.constraint(equalTo: uiView.bottomAnchor).isActive = true
loading_panels.topAnchor.constraint(equalTo: uiView.topAnchor).isActive = true
let view_temp = view_template(uid: _uid)
view_temp.test()
}
case “two”:
if let loading_panels = Bundle.main.loadNibNamed("xib_template_two”, owner: uiView, options: nil)?.first as? view_template_two {
loading_panels.translatesAutoresizingMaskIntoConstraints = false
uiView.addSubview(loading_panels)
loading_panels.leadingAnchor.constraint(equalTo: uiView.leadingAnchor).isActive = true
loading_panels.trailingAnchor.constraint(equalTo: uiView.trailingAnchor).isActive = true
loading_panels.bottomAnchor.constraint(equalTo: uiView.bottomAnchor).isActive = true
loading_panels.topAnchor.constraint(equalTo: uiView.topAnchor).isActive = true
}
default:
print("error")
}
You are using different initializers here:
When you say let view_temp = view_template(uid: _uid), init (uid: String) is used and your implementation sets _uid so it is not nil.
When you load a view from a XIB, init?(coder aDecoder: NSCoder) is used and this does not set _uid so it is nil.
To inject _uid into your templates, simply say loading_panels._uid = _uid in your two if let loading_panels = ... blocks.
You might also want to read section "Follow case conventions" in the Swift API Design Guidelines to brush up on your naming.

How to input text using the buttons of an in-app custom keyboard

I've made an in-app custom keyboard that replaces the system keyboard and pops up when I tap inside a UITextField.
Here is my code:
class ViewController: UIViewController {
var myCustomKeyboard: UIView!
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let keyboardNib = UINib(nibName: "Keyboard", bundle: nil)
myCustomKeyboard = keyboardNib.instantiateWithOwner(self, options: nil)[0] as! UIView
textField.inputView = myCustomKeyboard
}
}
The keyboard layout is loaded from an xib file.
Question
How do I get the button text into the text field?
Notes:
There are many tutorials about making a custom system keyboard (which needs to be installed), but I only want an in-app keyboard. The tutorials use a special view controller just for the keyboard, but here it seems that I am just setting the keyboard view.
I have read the Custom Views for Data Input documentation.
This is the closest Stack Overflow question I could find, but it doesn't go as far as describing how to get the text from the buttons.
Update
This tutorial seems to indicate that there is a view controller for the custom input view. However, I am getting lost in the Objective-C code. What would be the process in Swift?
This answer mentions the UIKeyInput protocol that UITextField conforms to, but how do I use it?
If there is any built in way too make a custom in-app keyboard, I would really prefer that to making a normal custom view.
I imagine something like this:
A new function to handle button event
func updateTextfield(sender: UIButton) {
textField.text = (textField.text ?? "") + (sender.titleForState(.Normal) ?? "")
}
And after init your custom keyboard, register the buttons:
myCustomKeyboard.subviews
.filter { $0 as? UIButton != nil } // Keep the buttons only
.forEach { ($0 as! UIButton).addTarget(self, action: "updateTextfield", forControlEvents: .TouchUpInside)}
Setup
Make an xib file that includes all your keys
Use Autolayout so that the keys will resize to the correct proportions no matter how big the keyboard is set to later.
Create a swift file with the same name as the xib file and set it as the file owner in the xib file setting.
Hook up all the key buttons to an IBAction method in the swift file. (See the code below.)
Code
I'm using the delegate pattern to communicate between the custom keyboard view and the main view controller. This allows them to be decoupled. Multiple different custom keyboards could be swapped in and out without needing to change the detailed implementation code in the main view controller.
Keyboard.swift file
import UIKit
protocol KeyboardDelegate {
func keyWasTapped(character: String)
}
class Keyboard: UIView {
var delegate: KeyboardDelegate?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeSubviews()
}
override init(frame: CGRect) {
super.init(frame: frame)
initializeSubviews()
}
func initializeSubviews() {
let xibFileName = "Keyboard" // xib extention not needed
let view = NSBundle.mainBundle().loadNibNamed(xibFileName, owner: self, options: nil)[0] as! UIView
self.addSubview(view)
view.frame = self.bounds
}
#IBAction func keyTapped(sender: UIButton) {
self.delegate?.keyWasTapped(sender.titleLabel!.text!)
}
}
Main view controller
Note that the ViewController conforms to the KeyboardDelegate protocol that we created. Also, when creating an instance of the keyboard view, the height needs to be set but the width doesn't. Apparently setting the inputView of the text field updates the keyboard view width to the screen width, which is convenient.
class ViewController: UIViewController, KeyboardDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// get an instance of the Keyboard (only the height is important)
let keyboardView = Keyboard(frame: CGRect(x: 0, y: 0, width: 0, height: 300))
// use the delegate to communicate
keyboardView.delegate = self
// replace the system keyboard with the custom keyboard
textField.inputView = keyboardView
}
// required method for keyboard delegate protocol
func keyWasTapped(character: String) {
textField.insertText(character)
}
}
Sources
The suggestions in #ryancrunchi's comment were helpful.
This answer from Creating a reusable UIView with xib (and loading from storyboard)
Related
A Swift example of Custom Views for Data Input (custom in-app keyboard)

Custom UIView subclass with XIB in Swift

I'm using Swift and Xcode 6.4 for what it's worth.
So I have a view controller that will be containing some multiple pairs of UILabels and UIImageViews. I wanted to put the UILabel-UIImageView pair into a custom UIView, so I could simply reuse the same structure repeatedly within the aforementioned view controller. (I'm aware this could be translated into a UITableView, but for the sake of ~learning~ please bear with me). This is turning out to be a more convoluted process than I imagined it would be, I'm having trouble figuring out the "right" way to make this all work in IB.
Currently I've been floundering around with a UIView subclass and corresponding XIB, overriding init(frame:) and init(coder), loading the view from the nib and adding it as a subview. This is what I've seen/read around the internet so far. (This is approximately it: http://iphonedev.tv/blog/2014/12/15/create-an-ibdesignable-uiview-subclass-with-code-from-an-xib-file-in-xcode-6).
This gave me the problem of causing an infinite loop between init(coder) and loading the nib from the bundle. Strangely none of these articles or previous answers on stack overflow mention this!
Ok so I put a check in init(coder) to see if the subview had already been added. That "solved" that, seemingly. However I started running into an issue with my custom view outlets being nil by the time I try to assign values to them.
I made a didSet and added a breakpoint to take a look...they are definitely being set at one point, but by the time I try to, say, modify the textColor of a label, that label is nil.
I'm kind of tearing my hair out here.
Reusable components seem like software design 101, but I feel like Apple is conspiring against me. Should I be looking to use container VCs here? Should I just be nesting views and having a stupidly huge amount of outlets in my main VC? Why is this so convoluted? Why do everyone's examples NOT work for me?
Desired result (pretend the whole thing is the VC, the boxes are the custom uiviews I want):
Thanks for reading.
Following is my custom UIView subclass. In my main storyboard, I have UIViews with the subclass set as their class.
class StageCardView: UIView {
#IBOutlet weak private var stageLabel: UILabel! {
didSet {
NSLog("I will murder you %#", stageLabel)
}
}
#IBOutlet weak private var stageImage: UIImageView!
var stageName : String? {
didSet {
self.stageLabel.text = stageName
}
}
var imageName : String? {
didSet {
self.stageImage.image = UIImage(named: imageName!)
}
}
var textColor : UIColor? {
didSet {
self.stageLabel.textColor = textColor
}
}
var splatColor : UIColor? {
didSet {
let splatImage = UIImage(named: "backsplat")?.tintedImageWithColor(splatColor!)
self.backgroundColor = UIColor(patternImage: splatImage!)
}
}
// MARK: init
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
setup()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup() {
if let view = NSBundle.mainBundle().loadNibNamed("StageCardView", owner: self, options: nil).first as? StageCardView {
view.frame = bounds
view.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
addSubview(view)
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
}
EDIT: Here's what I've been able to get so far...
XIB:
Result:
Problem: When trying to access label or image outlets, they are nil. When checking at breakpoint of said access, the label and image subviews are there and the view hierarchy is as expected.
I'm OK with doing this all in code if thats what it takes, but I'm not huge into doing Autolayout in code so I'd rather not if there's a way to avoid it!
EDIT/QUESTION SHIFT:
I figured out how to make the outlets stop being nil.
Inspiration from this SO answer: Loaded nib but the view outlet was not set - new to InterfaceBuilder except instead of assigning the view outlet I assigned the individual component outlets.
Now this was at the point where I was just flinging shit at a wall and seeing if it'd stick. Does anyone know why I had to do this? What sort of dark magic is this?
General advice on view re-use
You're right, re-usable and composable elements is software 101. Interface Builder is not very good at it.
Specifically, xibs and storyboard are great ways to define views by re-using views that are defined in code. But they are not very good for defining views that you yourself wish to re-use within xibs and storyboards. (It can be done, but it is an advanced exercise.)
So, here's a rule of thumb. If you are defining a view that you want to re-use from code, then define it however you wish. But if you are defining a view that you want to be able to re-use possibly from within a storyboard, then define that view in code.
So in your case, if you're trying to define a custom view which you want to re-use from a storyboard, I'd do it in code. If you are dead set on defining your view via a xib, then I'd define a view in code and in its initializer have it initialize your xib-defined view and configure that as a subview.
Advice in this case
Here's roughly how you'd define your view in code:
class StageCardView: UIView {
var stageLabel = UILabel(frame:CGRectZero)
var stageImage = UIImageView(frame:CGRectZero)
override init(frame:CGRect) {
super.init(frame:frame)
setup()
}
required init(coder aDecoder:NSCoder) {
super.init(coder:aDecoder)
setup()
}
private func setup() {
stageImage.image = UIImage(named:"backsplat")
self.addSubview(stageLabel)
self.addSubview(stageImage)
// configure the initial layout of your subviews here.
}
}
You can now instantiate this in code and or via a storyboard, although you won't get a live preview in Interface Builder as is.
And alternatively, here's roughly how you might define a re-usable view based fundamentally on a xib, by embedding the xib-defined view in a code-defined view:
class StageCardView: UIView {
var embeddedView:EmbeddedView!
override init(frame:CGRect) {
super.init(frame:frame)
setup()
}
required init(coder aDecoder:NSCoder) {
super.init(coder:aDecoder)
setup()
}
private func setup() {
self.embeddedView = NSBundle.mainBundle().loadNibNamed("EmbeddedView",owner:self,options:nil).lastObject as! UIView
self.addSubview(self.embeddedView)
self.embeddedView.frame = self.bounds
self.embeddedView.autoresizingMask = .FlexibleHeight | .FlexibleWidth
}
}
Now you can use the code-defined view from storyboards or from code, and it will load its nib-defined subview (and there's still no live preview in IB).
I was able to work it around but the solution is a little bit tricky. It's up to debate if the gain is worth an effort but here is how I implemented it purely in interface builder
First I defined a custom UIView subclass named P2View
#IBDesignable class P2View: UIView
{
#IBOutlet private weak var titleLabel: UILabel!
#IBOutlet private weak var iconView: UIImageView!
#IBInspectable var title: String? {
didSet {
if titleLabel != nil {
titleLabel.text = title
}
}
}
#IBInspectable var image: UIImage? {
didSet {
if iconView != nil {
iconView.image = image
}
}
}
override init(frame: CGRect)
{
super.init(frame: frame)
awakeFromNib()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func awakeFromNib()
{
super.awakeFromNib()
let bundle = Bundle(for: type(of: self))
guard let view = bundle.loadNibNamed("P2View", owner: self, options: nil)?.first as? UIView else {
return
}
view.translatesAutoresizingMaskIntoConstraints = false
addSubview(view)
let bindings = ["view": view]
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat:"V:|-0-[view]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings)
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat:"H:|-0-[view]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings)
addConstraints(verticalConstraints)
addConstraints(horizontalConstraints)
}
titleLabel.text = title
iconView.image = image
}
This is how it looks like in interface builder
This is how I embedded this custom view in the example view controller defined on a storyboard. Properties of P2View are set in the attributes inspector.
There are 3 points worth mentioning
First:
Use the Bundle(for: type(of: self)) when loading the nib. This is because the interface builder renders the designables in the separate process which main bundle is not the same as your main bundle.
Second:
#IBInspectable var title: String? {
didSet {
if titleLabel != nil {
titleLabel.text = title
}
}
}
When combining IBInspectables with IBOutlets you have to remember that the didSet functions are called before awakeFromNib method. Because of that, the outlets are not initialized and your app will probably crash at this point. Unfortunatelly you cannot omit the didSet function because the interface builder won't render your custom view so we have to leave this dirty if here.
Third:
titleLabel.text = title
iconView.image = image
We have to somehow initialize our controls. We were not able to do it when didSet function was called so we have to use the value stored in the IBInspectable properties and initialize them at the end of the awakeFromNib method.
This is how you can implement a custom view on a Xib, embed it on a storyboard, configure it on a storyboard, have it rendered and have a non-crashing app. It requires a hack, but it's possible.

Resources