26

I tried to make @IBDesignable UIView subclass following this (link) tutorial. First custom view goes fine. But when I try to make another one, I have errors. First I got failed to update auto layout status: the agent crashed and Failed to render instance of .... Somehow I started to be able to biuld and run project with these errors, but then I get new error - EXC_BAD_ACCESS ... on line let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView. Here is whole method:

func loadViewFromNib() -> UIView {
        
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: "advancedCellView", bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
        
        return view
    }

With first custom UIView is no problem. I use same code..

enter image description here

Any ideas? Thank you

Community
  • 1
  • 1
Lachtan
  • 4,803
  • 6
  • 28
  • 34
  • 1
    You wrote, "First custom view goes fine." Does that mean that you are actually able to load the view once, but then if you try to load it again you get an error? – Aaron Rasmussen Jan 07 '16 at 16:48
  • 1
    Also, what is the reason for using `NSBundle:forClass:` instead of just accessing `NSBundle.mainBundle()`? Is this `nib` stored in a different bundle? – Aaron Rasmussen Jan 07 '16 at 16:49
  • 1
    That suggests that the problem is not with the code that loads and instantiates the `nib`, but with something in the `xib` file itself. Did you change the `class` for your view subclass in interface builder for the `xib`? – Aaron Rasmussen Jan 07 '16 at 16:53
  • By that I meant that first I created "simple" custom UIView subclass and it works just fine. I can use it without any errors. Then I created new xib file where I designed other component and I also created it's UIView subclass (both in screenshot). I followed same procedure and It's giving me errors I described in question. Ill think about your other suggestions and try to fix it. Thank you – Lachtan Jan 07 '16 at 17:30
  • It would be easier to answer this question if you provided some of the details regarding the nib set up, particularly those in the Identity and Attributes inspectors for the top level view and the File's Owner. Also, the full error you get with "Failed to render instance of..." might help narrow it down – Josh Heald Feb 29 '16 at 14:35

4 Answers4

51

The loadViewFromNib() method you post looks fine, you're getting the bundle correctly and specifying the nib name as a string literal, so it should find the nib. As someone else commented, there's probably something wrong with your set up of the nib instead. Here are a few things to check:

  1. You should have AdvancedCellView set as the File's Owner Custom Class in the Identity Inspector.
  2. Check that the module is correct too - usually you just want it blank.
  3. The Custom Class for the View should not be set, just leave it as the default (UIView). If you have a more descriptive name than 'View' in the sidebar, it's probably wrong.
  4. Check that you only have one top level object in the nib. The sidebar should look like this, without any other entries, when you have the View tree collapsed.

Nib hierarchy with only one top level view

N.B. This answer is related to the tutorial that the OP linked to, and not the only correct way to set up a nib.

Josh Heald
  • 3,907
  • 28
  • 37
  • 1
    Given that the OP's code is manually loading the nib and directly setting the `view` property, it's not clear whether setting the File's Owner's class in the nib file would be necessary. It wouldn't affect runtime behavior in any case, only the ability to make connections in Interface Builder. However, it's incorrect to say that the view's class should not be set in the nib (if indeed the OP want it to be a subclass of `UIView`); that *would* affect runtime behavior because that information is used by the unarchiver to determine which class to instantiate. – jlehr Feb 29 '16 at 18:35
  • @jlehr true, it's not always the case that the class should not be set in the nib, but the OP was following a tutorial which had him load the view from the nib in the `init(frame:)` and `init(coder:)` methods. In that case, setting the custom class for the view will lead to an infinite loop when loading the nib, unless you have other code to prevent that. – Josh Heald Feb 29 '16 at 21:19
  • Okay, I'm scratching my head -- in what circumstance would a subclass of `UIView` need to load another instance of its own type from a nib (leaving aside that nib-loading is generally a responsibility of view controller rather than view)? Maybe I'm missing something, but that tutorial sounds a little fishy. – jlehr Feb 29 '16 at 22:05
  • I'm a bit confused too now. The tutorial's approach is to let the ViewController's nib/storyboard load the nib by setting the custom class on a UIView placeholder in IB. That means that init is called on the custom class, where it unpacks a UIView from the custom nib and adds it to the view hierarchy. Personally, I find that's the best approach as the view is self contained, so it can load itself at both design time and run time. – Josh Heald Mar 01 '16 at 07:18
  • A view can also use `awakeAfterUsingCoder()` to return a replacement for itself, which I've seen used to load a view from the nib with a custom class set. I've never seen that approach work with `IBDesignable` though, and there's a bunch of other problems with it. – Josh Heald Mar 01 '16 at 07:21
12

I was looking at the tutorial and discovered that if you subclass the View element in your custom xib then you will get the error. Ensure you've only set the "File's Owner" to your custom class.

enter image description here

B-Rad
  • 353
  • 3
  • 11
2

The instantiateWithOwner(options:) method returns an array, not a view, so a forced downcast to UIView will never work. Instead, try casting to the actual type, [AnyObject].

The elements in the array correspond to top-level objects in the nib file, so the view you're interested in should be one of the array elements. Given the name of your nib file, in all likelihood there will only be one top-level object in the array -- the cell you're trying to load. Make sure that there's only one top-level object in the nib file, and that it is indeed an instance of a subclass of UIView.

Note that your implementation is potentially inefficient. If you're loading more than one cell, you should cache the UINib instance instead of creating a new one each time. Note that framework classes such as UITableViewController have built in methods for registering nibs that take care of these details automatically, so you may not actually need to do this yourself.

jlehr
  • 15,557
  • 5
  • 43
  • 45
  • 1
    The instantiateWithOwner(options:) method returns an array of top level objects unpacked from the nib, as you say. However, if those objects do not have a custom class set, they will be UIViews and the downcast will work just fine. I don't really think it should be a forced downcast, but that's another issue. – Josh Heald Feb 29 '16 at 14:33
  • Heh, you're right -- looks like I missed the `[0]` there. I'll update my answer. – jlehr Feb 29 '16 at 18:16
1

Try doing this instead. Always works for me:

let picker = NSBundle.mainBundle().loadNibNamed("advancedCellView", owner: nil, options: nil)
let view = picker[0] as! UIView

return view

Let me know if that works

brl214
  • 527
  • 1
  • 6
  • 16
  • Thanks for response. It doesn't work neither in first custom UIView. Throws `Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key captionLabel.'` – Lachtan Jan 07 '16 at 16:45
  • Thats a different issue. You have to connect the label in storyboard to your custom view class. – brl214 Jan 07 '16 at 17:42