Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Subclassing a UIView subclass loaded from a nib

I have a class, FooView, that is a subclass of UIView, and whose view is loaded from a nib, something like:

+ (instancetype)viewFromNib
{
    NSArray *xib = [[NSBundle mainBundle] loadNibNamed:@"FooView" owner:self options:nil];
    return [xib objectAtIndex:0];
}

The nib itself has its Custom Class set to FooView in the Identity Inspector.

This is instantiated as:

FooView *view = [FooView viewFromNib];

This behaves as you'd expect. However, when FooView is itself subclassed as FooSubclassView, and instantiated as:

FooSubclassView *view = [FooSubclassView viewFromNib];

view is still of type FooView, not FooSubclassView.

Swizzling the class with object_setClass doesn't fix the fact that the underlying object is an instance of FooView, and thus methods called on the subclass instance will be those of the superclass (FooView), not FooSubclassView.

How can I correct this so that subclasses are of the correct type, without having to create a new nib for every subclass, or having to reimplement viewFromNib in every subclass?

like image 724
jnic Avatar asked Sep 02 '13 01:09

jnic


2 Answers

Swizzling is not (ever) the answer.

The problem is in your NIB; it is archived with object[0] being an instance of FooView, not FooSubclassView. If you want to load the same NIB with a different view subclass as object[0], you need to move the instance out of the NIB's archive.

Probably the easiest thing to do, since your class is already loading the NIB, is make an instance of FooView or FooSubclassView the File's Owner.

This question has a decent explanation of the File's Owner pattern. Note that you are pretty much already there in that your class is what is loading the XIB/NIB anyway.

And here is the official docs on File's Owner.

like image 151
bbum Avatar answered Nov 13 '22 10:11

bbum


I'm not sure you are onto the best solution, but I think this is what you are looking for.

+ (instancetype)viewFromNib
{
    NSString *className = NSStringFromClass([self class]);
    NSArray *xib = [[NSBundle mainBundle] loadNibNamed:className owner:self options:nil];
    return [xib objectAtIndex:0];
}

That is as long as you can be sure that the NIB has the same name as the class.


After realizing that I mistook one of the requirements, I say I'll have to agree with @bbum.

- (id)init
{
    // NOTE: If you don't know the size, you can work this out after you load the nib.
    self = [super initWithFrame:GCRectMake(0, 0, 320, 480)];
    if (self) {
        // Load the nib using the instance as the owner.
        [[NSBundle mainBundle] loadNibNamed:@"FooView" owner:self options:nil];
    }
    return self;
}

+ (instancetype)viewFromNib
{
    return [[self alloc] init];
}
like image 36
Jeffery Thomas Avatar answered Nov 13 '22 11:11

Jeffery Thomas