Why does this init method when loading a nib retur

2019-09-02 05:54发布

问题:

Why does this init method return an object out of scope?

Using XCode 4.2, base SDK of 4.3, and ARC, I'm trying to load an UIView from a nib (not a UIViewController). I need to not use a UIViewController at all in the process.

After reading this answer to an S.O. question here, it looks like it can be done: How to load a UIView using a nib file created with Interface Builder (The answer by user "MusiGenesis" describes the process)

I created a sub-class of UIView with a single label:

@interface MyView : UIView
@property (unsafe_unretained, nonatomic) IBOutlet UILabel *textLabel;
@end

In the implementation I override initWithFrame:

- (id)initWithFrame:(CGRect)frame
{
    //self = [super initWithFrame:frame];
    self = [JVUIKitUtils initWithNibName:@"MyView" withOwner:self];
    if( self )
    {
        NSLog(@"Created");
    }
    return self;
}

In I.B. I created a file named "MyView.xib" with a single view. It has a label as a sub-view, and I created the label property by dragging it to the h file.

And in another file, I created this re-usable static method:

+ (id)initWithNibName:(NSString*)nibName withOwner:(id)uiView
{
    id object = nil;
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName owner:uiView options:nil]; // 1 object, out of scope
    for( id tempObject in bundle)
    {
        if( [tempObject isKindOfClass:[uiView class]] ) object = tempObject;
        break;
    }
    return object;
}

As you can see in the following screen shot, the bundle has one object reference, but it's out of scope.

And debugging:

This is my code for instantiation:

subView = [[MyView alloc] initWithFrame:CGRectZero]; // ok
NSAssert(subView != nil, @"MyView was nil"); // fail

Any ideas on why the other S.O. poster was able to get it to work but this does not?

回答1:

The use of the owner seems a bit confusing in the way that you are loading a nib. It appears that you are trying to use the view as both the owner of the nib and the first object in it.

Are you trying to load MyView from your nib (i.e. is the class of the view inside your nib files defined as MyView) or are you trying to load a subview of MyView from the nib?

If the view inside your nib is a MyView, here's how to load it. Create this static method as a category on UIView:

@implementation UIView (NibLoading)

+ (id)viewWithNibName:(NSString*)nibName
{
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName owner:nil options:nil];
    if ([bundle count])
    {
        UIView *view = [bundle objectAtIndex:0];
        if ([view isKindOfClass:self])
        {
            return view;
        }
        NSLog(@"The object in the nib %@ is a %@, not a %@", nibName, [view class], self);
    }
    return nil;
}

@end

That will let you load any kind of view from a nib file (the view needs to be the first item defined in the nib). You would create your view like this:

MyView *view = [MyView viewWithNibName:@"MyView"];

If the view inside the nib is not a MyView, but you want to load it as a subview of MyView, with MyView defined as the file's owner in the nib file, do it like this:

@implementation UIView (NibLoading)

- (void)loadContentsFromNibName:(NSString*)nibName
{
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil];
    if ([bundle count])
    {
        UIView *view = [bundle objectAtIndex:0];
        if ([view isKindOfClass:[UIView class]])
        {
            //resize view to fit
            view.frame = self.bounds;

            //add as subview
            [self addSubview:view];
        }
        NSLog(@"The object in the nib %@ is a %@, not a UIView", nibName, [view class]);
    }
}

@end

Using that approach, just create your view as normal using initWithFrame, then call loadContentsFromNibName to loa the contents from a nib. You would load your view like this:

MyView *view = [[MyView alloc] initWithFrame:CGRect(0,0,100,100)];
[view loadContentsFromNibName:@"MyView"];


回答2:

Your [JVUIKitUtils initWithNibName:@"MyView" withOwner:self] approach is all wrong. When an init method is called, the object is already allocated by the call to [MyView alloc]. The reason why you chain the calls to the super init method is so that this will daisy chain all the way down to the NSObject init method, which simply returns the instance it is.

By setting "self" to the (autoreleased) instance value returned by your JVUIKitUtils, you are effectively setting it to a memory address other than what was allocated by the call to [MyView alloc], which generates the out of scope error.

Instead, don't create the init method your are trying to create. You already have the method to create and initialize your nib-based view in your state method. I would change the name and do something like:

+ (UIView)newViewWithNibName:(NSString*)nibName withClassType:(Class)uiView
{
    id object = nil;

    // you need some object to assign nib file owner to. use an NSObject.
    NSObject* owner = [[NSObject alloc] init];
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName owner:owner options:nil]; // 1 object, out of scope
    for( id tempObject in bundle)
    {
        if( [tempObject isKindOfClass:] ) object = [tempObject retain];
        break;
    }
    [owner release];
    return object;
}

and then call it like:

MyView* subView = [JVUIKitUtils viewWithNibName:@"MyView" withClassType:[MyView class]];

I haven't tested this code. Your milage might vary.