9
votes

I have some custom created UIViews that use .xib files for layout, and backing classes for extra setup. I create these classes using alloc/init and calling loadNibNamed in my custom init method but in doing so was causing memory leak. Someone pointed out that the alloc portion actually created a self object that was leaking so I adjusted my init method to this one:

- (id)init 
{
    [self autorelease];
    self = [[[[NSBundle mainBundle] loadNibNamed:@"AssignmentView" owner:nil options:nil] lastObject] retain];
    [self setupBranding];

    UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapDetected:)];
    [self addGestureRecognizer:tapRecognizer];
    [tapRecognizer release];

    return self;
}

However, now when I run analyze code I get this warning "Returning 'self' while it is not set to the result of '[(super or self) init...]'". So my question is what is the correct way for doing custom UIViews with a backing class?

Since it was asked I'd used this above code like this:

AssignmentView * assignmentView = [[AssignmentView alloc] init];
[self.view addSubview:assignmentView];
2
Generally, if you load a view from an xib, you should be loading it through loadNibNamed in whatever class you're creating it in and then setting it up in its awakeFromNib method, not even touching init. This has been my experience, anyway. Could you perhaps add some code where you are instantiating the view, and your awakeFromNib method?Paul O.
I think you would just add self = [super init] at the top of your method. Paul is right though, in that you normally aren't doing the nib loading internally.Dustin
Thanks, I've been trying to avoid the whole loadNibNamed and just use [[MyView alloc] init] instead, but if I have to re-work that and instead use the loadNibNamed then I guess I have to. I don't have an awakeFromNib method but everything in the init would go there.Rob Booth

2 Answers

18
votes

Get the view from -[NSBundle loadNibNamed:owner:options:], then set its frame to match your view's bounds. Generally you'll also want to make the view in the nib resize when its parent view is resized.

We've got a UIView category with these additions:

- (UIView *)viewFromNib
{
    Class class = [self class];
    NSString *nibName = NSStringFromClass(class);
    NSArray *nibViews = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil];
    UIView *view = [nibViews objectAtIndex:0];
    return view;
}


- (void)addSubviewFromNib
{
    UIView *view = [self viewFromNib];
    view.frame = self.bounds;
    [self addSubview:view];
}

Then our -initWithFrame: method looks like this:

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self addSubviewFromNib];
    }
    return self;
}

Just make sure the nib has the same name as the class for this to work.

7
votes

Would you consider using the convenience constructor style?

+ (AssignmentView *)assignmentView
{
    AssignmentView *result = [[[NSBundle mainBundle] loadNibNamed:@"AssignmentView" owner:nil options:nil] lastObject];
    [result setupBranding];

    UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapDetected:)];
    [result addGestureRecognizer:tapRecognizer];
    [tapRecognizer release];

    return result;
}

It gives you the flexibility you seem to need when construct your view, but doesn't cause a memory leak or warnings.