Почему этот метод init при загрузке nib возвращает объект из области видимости?

Почему этот метод init возвращает объект из области видимости?

Используя XCode 4.2, базовый SDK 4.3 и ARC, я пытаюсь загрузить UIView из ниба (а не UIViewController). Мне не нужно вообще использовать UIViewController в этом процессе.

После прочтения этого ответа на S.O. вопрос здесь, похоже, это можно сделать: Как загрузить UIView с помощью файла nib, созданного с помощью Interface Builder (Ответ пользователя "MusiGenesis" описывает процесс)

Я создал подкласс UIView с одной меткой:

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

В реализации я переопределяю initWithFrame:

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

В I.B. Я создал файл с именем "MyView.xib" с одним представлением. Он имеет метку в качестве подзадачи, и я создал свойство label, перетащив его в h файл. MyView nib file setup

И в другом файле я создал этот повторно используемый статический метод:

+ (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;
}

Как вы можете видеть на следующем снимке экрана, в комплекте есть одна ссылка на объект, но она выходит за рамки.

И отладка: MyView is out of scope

Это мой код для создания экземпляра:

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

Любые идеи о том, почему другой С.О. плакат смог заставить его работать, но это не так?

Ответ 1

Использование владельца кажется немного запутанным в том, как вы загружаете наконечник. Похоже, что вы пытаетесь использовать представление как владельца элемента, так и первого объекта в нем.

Вы пытаетесь загрузить MyView из своего nib (т.е. является классом представления внутри ваших файлов nib, определенных как MyView), или вы пытаетесь загрузить подвью MyView из nib?

Если представление внутри вашего nib - это MyView, вот как его загрузить. Создайте этот статический метод как категорию в 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

Это позволит вам загружать любой вид из файла nib (представление должно быть первым элементом, определенным в nib). Вы бы создали свое представление следующим образом:

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

Если представление внутри nib не является MyView, но вы хотите загрузить его как подвью MyView, MyView определяется как владелец файла в файле nib, выполните следующие действия:

@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

Используя этот подход, просто создайте свое представление как обычно, используя initWithFrame, а затем вызовите loadContentsFromNibName, чтобы загрузить содержимое из ниба. Вы загрузите свое мнение следующим образом:

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

Ответ 2

Ваш подход [JVUIKitUtils initWithNibName:@"MyView" withOwner:self] ошибочен. Когда вызывается метод init, объект уже назначается вызовом [MyView alloc]. Причина, по которой вы связываете вызовы с методом суперинтеграции, заключается в том, что это приведет к полной цепочке вплоть до метода init NSObject, который просто возвращает экземпляр, который он есть.

Установив "self" в значение экземпляра (autoreleased), возвращаемое вашим JVUIKitUtils, вы фактически устанавливаете его на адрес памяти, отличный от того, что было выделено вызовом [MyView alloc], который генерирует вне рамки ошибка.

Вместо этого не создавайте метод init, который вы пытаетесь создать. У вас уже есть способ создания и инициализации вашего представления на основе nib в вашем методе состояния. Я бы сменил имя и сделал что-то вроде:

+ (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;
}

а затем назовите его так:

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

Я не тестировал этот код. Ваше возражение может измениться.