24

I'm developing an iOS 5+ app with latest SDK.

I have created a custom UIView (TopMenuView) with a custom XIB. On Interface Builder I have changed, on this XIB, UIView class to TopMenuView. I haven't set any File's Owner.

On TopMenuView.m I have:

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];

    if (self)
    {
        NSLog(@"init with coder: %d", counter);
        counter++;
        // Add custom XIB
        NSArray *topMenuView = [[NSBundle mainBundle] loadNibNamed:@"TopMenuView"
                                                             owner:nil
                                                           options:nil];
        UIView *nv = [topMenuView objectAtIndex:0];

        [self addSubview:nv];
    }

    return self;
}

Using Interface Builder I have added a UIView to a UIViewController and changed this UIView class to TopMenuView.

But, when I run the app, I get this log message 4251 times: 2013-10-13 20:49:34.078 MyProject[470:c07] init with coder: 0

And then, I get an EXC_BAD_ACCESS here:

NSArray *topMenuView = [[NSBundle mainBundle] loadNibNamed:@"TopMenuView"
                                                             owner:nil
                                                           options:nil];
VansFannel
  • 45,055
  • 107
  • 359
  • 626
  • you are adding nv as a subview but where is you main view? you are not declaring any view to file's owner. you have to attach one view to file's owner . – KDeogharkar Oct 14 '13 at 07:16

5 Answers5

72

The reason it's calling the initWithCoder so many times is due to wrong class setup in your .xib file.

Make sure the Custom Class on the File's Owner is your custom UIView class:

enter image description here

And make sure the class on the root View is the default UIView:

enter image description here

And now this is all you need in your custom class (in Swift):

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)

    let view = NSBundle.mainBundle().loadNibNamed("TopMenuView", owner: self, options: nil)[0] as! UIView
    self.addSubview(view)
    view.frame = self.bounds
}
JYeh
  • 4,273
  • 3
  • 27
  • 40
  • 1
    This really solves the problem. Just felt that Xcode should have provided better warnings to these kind of `custom class` configurations. – RandyTek Jun 02 '16 at 05:02
  • 1
    @JYeh This is one detailed and precise answer. – Singh Jun 30 '16 at 03:28
  • 2
    Also note that, if your custom class is from another project, like a Cocoapods dependency, then you need to fill in the Module field too (just below the custom class). – judepereira Mar 26 '17 at 14:35
  • It takes me days to figure this out. Why can't apple gives some more meaningful error messages? – Sira Lam Feb 04 '18 at 13:11
  • You're a hero for this one. – ScottyBlades Jun 22 '18 at 01:31
  • Dude... this is the second time I've done this and this saved me both times lol. Accidentally set the root view to the custom class instead of UIView both times. – TJ Olsen Sep 25 '20 at 02:00
  • I wish this appeared above the accepted "answer", so I didn't waste my time even reading their one. Good stuff man. – user3690202 Jun 24 '21 at 03:33
11

You are most likely getting into an infinite loop because you're recursively calling initWithCoder. One workaround is to check if your subclass has any subviews first.

-(id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        if (self.subviews.count == 0) {
            NSArray *topMenuView = [[NSBundle mainBundle] loadNibNamed:@"TopMenuView" owner:nil options:nil];
            UIView *nv = [topMenuView objectAtIndex:0];
            [self addSubview:self.view];
        }
    }
    return self;
}
Sebyddd
  • 4,305
  • 2
  • 39
  • 43
3

This is how I did it:

//Add Custom View to my main view of viewcontroller
self.customNavView = [[CustomNavigationView alloc] init];
self.customNavView = [[[NSBundle mainBundle] loadNibNamed:@"CustomNavigationView" owner:self options:nil] objectAtIndex:0];
[self.customNavView setFrame:CGRectMake(0, 20, 320, 54)];
[self.view addSubview:self.customNavView];    

Here CustomNavigationView is a UIView subclass with Files Owner Class as UIView and the UIView custom class as CustomNavigationView.

This works for me.

Geekoder
  • 1,531
  • 10
  • 21
1

Your - (id)initWithCoder:(NSCoder *)aDecoder is called whenever TopMenuView is created by loading your xib.

Thus you are recursively calling your initWithCoder:


Comment all your method - (id)initWithCoder:(NSCoder *)aDecoder

and where you want to use TopMenuView probably in some controller use the below code

NSArray *topMenuView = [[NSBundle mainBundle] loadNibNamed:@"TopMenuView" owner:nil options:nil];
UIView *nv = [topMenuView objectAtIndex:0];
Inder Kumar Rathore
  • 39,458
  • 17
  • 135
  • 184
0

I was getting the same error until I redid how I loaded the xib file from the storyboard. Basically it involved making an @IBOutlet from the xib file's root view to the code. Also make sure that you set the File's Owner of the xib to your custom class.

import UIKit
class ResuableCustomView: UIView {

    @IBOutlet var view: UIView!
    @IBOutlet weak var label: UILabel!

    @IBAction func buttonTap(sender: UIButton) {
        label.text = "Hi"
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        NSBundle.mainBundle().loadNibNamed("ReusableCustomView", owner: self, options: nil)[0] as! UIView
        self.addSubview(view)
        view.frame = self.bounds
    }
}

My full answer for setting up this project is here.

Community
  • 1
  • 1
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393