Путаница с iOS loadNibNamed, что лучше всего?

Я знаком с большей частью процесса создания XIB для моего собственного подкласса UIView, но не все у меня работает должным образом - в основном это связано с подключением IBOutlets. Я могу заставить их работать окольными путями.

Моя установка такова:

  • У меня есть MyClass.h и MyClass.m. У них есть IBOutlets для UIView (называемого представлением) и UILabel (называемого myLabel). Я добавил свойство «просмотр», потому что некоторые примеры в Интернете, казалось, предполагали, что вам это нужно, и это фактически решило проблему, из-за которой я получал сбой, потому что не мог найти свойство просмотра, я думаю, даже не в родительском классе UIView .
  • У меня есть XIB-файл с именем MyClass.xib, а его пользовательский класс File Owner — MyClass, который предварительно заполнен правильно после того, как мои .h и .m для этого класса существовали.

Мой метод инициализации - это то место, где у меня возникают проблемы.

Я попытался использовать метод «loadNibNamed» основного пакета NSBundle и установить владельца как «я», надеясь, что я создам экземпляр представления, и он автоматически сопоставит свои выходы с выходами в моем классе (я знаю как это сделать, и я осторожен с этим). Затем я подумал, что хочу сделать «я» равным подвиду с индексом 0 в этом наконечнике, а не делать

self = [super init];

или что-то в этом роде.

Я чувствую, что я делаю что-то неправильно здесь, но примеры в Интернете имели аналогичные вещи, происходящие в методе init, но они присваивают это подпредставление 0 свойству представления и добавляют его как дочерний - но разве это не в общей сложности два экземпляра MyClass? Один по существу не связан с IBOutlets, содержащий дочерний экземпляр MyClass, созданный с помощью loadNibNamed? Или, в лучшем случае, это не экземпляр MyClass с дополнительным промежуточным UIView, содержащим все IBOutlets, которые я изначально хотел сделать прямыми дочерними элементами MyClass? Это немного раздражает, когда дело доходит до таких вещей, как instanceOfMyClass.frame.size.width, поскольку он возвращает 0, когда представленный дочерний UIView возвращает реальный размер кадра, который я искал.

Что я делаю неправильно, так это то, что я возился с loadNibNamed внутри метода инициализации? Должен ли я делать что-то подобное?

MyClass *instance = [[MyClass alloc] init];
[[NSBundle mainBundle] loadNibNamed:@"MyClass" owner:instance options:nil];  

Или вот так?

MyClass *instance = [[[NSBundle mainBundle] loadNibNamed:@"MyClass" owner:nil options:nil] objectAtIndex:0]; 

Заранее спасибо за любую помощь.


person Cloov    schedule 23.11.2012    source источник
comment
В какой-то момент вы говорите, что создаете подкласс UIView, а затем пишете о том, что внутри него есть IBOutlets для подключения к представлениям и меткам. Это довольно необычно. Обычно вы создаете контроллер представления и предоставляете ему представления для управления, будь то стандартные или пользовательские.   -  person Phillip Mills    schedule 23.11.2012
comment
Ваш первый пример создает два экземпляра MyClass, что, вероятно, не то, что вам нужно. К сожалению, я не знаю, что здесь предложить, потому что использование вами MyClass неясно. Как вы собираетесь использовать MyClass? В частности, что вы делали, чтобы увидеть сбой, потому что он не смог найти свойство просмотра? Это заставляет меня подозревать, что у вас есть некоторая путаница между представлениями и контроллерами.   -  person Jonah    schedule 23.11.2012


Ответы (3)


Второй вариант правильный. Самый защитный код, который вы могли бы сделать, выглядит так:

+ (id)loadNibNamed:(NSString *)nibName ofClass:(Class)objClass {
    if (nibName && objClass) {
        NSArray *objects = [[NSBundle mainBundle] loadNibNamed:nibName 
                                                         owner:nil 
                                                       options:nil];            
        for (id currentObject in objects ){
            if ([currentObject isKindOfClass:objClass])
                return currentObject;
        }
    }

    return nil;
}

И вызовите так:

MyClass *myClassInstance = [Utility loadNibNamed:@"the_nib_name" 
                                         ofClass:[MyClass class]]; 
// In my case, the code is in a Utility class, you should 
// put it wherever it fits best

Я предполагаю, что ваш MyClass является подклассом UIView? Если это так, вам нужно убедиться, что UIView вашего .xib действительно относится к классу MyClass. Это определяется на третьей вкладке в правой части построителя интерфейса после выбора представления.

person Ismael    schedule 23.11.2012
comment
Я относительно новичок в работе с перьями и много раз видел этот шаблон (загрузить перо, вытащить один объект из возвращаемого массива, продолжить). Это кажется очень неэффективным, поскольку он создает несколько экземпляров объектов, а затем отбрасывает все, кроме одного. например, если у меня есть перо, содержащее 5 вещей, и я следую приведенному выше шаблону, чтобы создать экземпляры для каждого из них, я в основном создал 25 экземпляров и выбросил 20. Кажется, имеет больше смысла стиснуть зубы и создать 5 отдельных перьев. Сохраняет циклы, а также защитный код, подобный приведенному выше. - person Lee Fastenau; 26.03.2015
comment
Ну да, это предназначено для использования одного NIB одного класса UIView. Я не вижу, где я предложил другое. Если у вас есть NIB с несколькими представлениями, вам придется настроить метод, чтобы найти каждое представление соответственно. - person Ismael; 26.03.2015

Все, что вам нужно сделать, это создать подвид через loadNibNamed, установить фрейм и добавить его в подвид. Например, я добавляю три подпредставления, используя класс MyView, который является подклассом UIView, интерфейс которого определен в NIB, MyView.xib:

Итак, я определяю initWithFrame для своего подкласса UIView:

- (id)initWithFrame:(CGRect)frame
{
    NSLog(@"%s", __FUNCTION__);

    self = [super initWithFrame:frame];
    if (self)
    {
        NSArray *nibContents = 
          [[NSBundle mainBundle] loadNibNamed:@"MyView" 
                                        owner:self 
                                      options:nil];
        [self addSubview:nibContents[0]];
    }
    return self;
}

Итак, например, в моем UIViewController я могу загрузить пару этих подклассов UIView объектов следующим образом:

for (NSInteger i = 0; i < 3; i++)
{
    CGRect frame = CGRectMake(0.0, i * 100.0 + 75.0, 320.0, 100.0);
    MyView *myView = [[MyView alloc] initWithFrame:frame];
    [self.view addSubview:myView];

    // if you want, do something with it: 
    // Here I'm initializing a text field and label

    myView.textField.text = [NSString stringWithFormat:@"MyView textfield #%d",
                              i + 1];
    myView.label.text = [NSString stringWithFormat:@"MyView label #%d", 
                          i + 1];
}

Первоначально я советовал использовать контроллеры, и я оставлю этот ответ ниже для исторической справки.


Исходный ответ:

Я не вижу здесь ссылок на просмотр контроллеров. Обычно у вас будет подкласс UIViewController, экземпляр которого вы затем создадите с помощью

MyClassViewController *controller = 
  [[MyClassViewController alloc] initWithNibName:@"MyClass" 
                                          bundle:nil];

// then you can do stuff like
//
// [self presentViewController:controller animated:YES completion:nil];

Файл NIB, MyClass.xib, может указать, что базовый класс для UIView, если хотите, где у вас есть весь код, связанный с представлением (например, предполагая, что MyClass был подклассом UIView).

person Rob    schedule 23.11.2012
comment
Спасибо. Я избегал использования контроллера, потому что, по сути, я создаю довольно сложный пользовательский интерфейс из подклассов в отношениях «один ко многим», и поэтому я рассматривал MyClass как представление, которое реплицируется несколько раз. Поскольку я не слишком много работаю с ними, кроме как заполняю их один раз, я подумал, что лучше всего рассматривать их как массив и заполнять их из одного родительского «контроллера» (то есть ViewController). - person Cloov; 23.11.2012
comment
Еще раз спасибо! В вашем примере, где вы используете MyView *myView = [[MyView alloc] initWithFrame:frame], создается ли это автоматически с использованием файла XIB? Ваш первый пример, который использует [[NSBundle mainBundle] loadNibNamed:@MyView owner:self options:nil], может работать для меня, если (а) я могу привести его к типу MyView и немного поработать с ним, прежде чем просто добавить его в качестве подпредставления , и (b) что, будучи созданным с помощью loadNibNamed, он вызовет метод инициализации, который я могу переопределить для выполнения моей конфигурации на основе файла MyView.m. - person Cloov; 24.11.2012
comment
@Cloov Думаю, я не ясно выразился. Я предлагаю вам (а) определить версию initWithFrame для вашего подкласса UIView, которая загружает представление из NIB; и (б) ваш контроллер представления (или что-то еще), который теперь хочет использовать ваш подкласс UIView, должен только вызвать initWithFrame, а метод, который мы написали на шаге (а), позаботится о его загрузке из NIB. - person Rob; 24.11.2012
comment
Это решение приводит к созданию двух объектов, один из которых принадлежит родительскому контроллеру, а другой создается внутри initWithFrame. Обязательно сохраните ссылку на представление, которое вы загружаете из пера, если хотите получить доступ к каким-либо выходам. - person BillyRayCyrus; 28.08.2014
comment
Чтобы избежать проблемы с созданием двух представлений, вы можете использовать: return nibContents[0]; в initWithFrame, чтобы использовать представление, загруженное только из Nib. - person BillyRayCyrus; 28.08.2014
comment
По дизайну перья загружают свои управляющие файлы, а не наоборот. Я думаю, что намерение программиста становится намного яснее, когда мы заставляем вызывающую сторону создавать экземпляр представления из пера, говоря об этом явно. - person smileBot; 08.05.2017

Вот один метод, который я использую:

  1. Создайте подкласс для UIView, он будет называться MyClass
  2. Создайте xib-файл представления. Откройте в построителе интерфейса, нажмите «Владелец файла» и в инспекторе удостоверений измените класс на класс вашего родительского контроллера представления, например. ParentViewController.
  3. Щелкните представление, уже находящееся в списке объектов, и измените его класс в Identity Inspector на MyClass.
  4. Любые выходы/действия, которые вы объявляете в MyClass, будут связаны путем перетаскивания щелчком мыши из представления (не владельца файла). Если вы хотите связать их с переменными из ParentViewController, щелкните и перетащите их из владельца файла.
  5. Теперь в вашем ParentViewController вам нужно объявить переменную экземпляра для MyClass.

ParentViewController.h добавить следующее:

@class MyClass

@interface ParentViewController : UIViewController {
    MyClass *myClass;
}

@property (strong, nonatomic) MyClass *myClass;

Синтезируйте это в своей реализации и добавьте следующее в метод viewDidLoad:

- (void)viewDidLoad {
    [super viewDidLoad];

    [[NSBundle mainBundle] loadNibNamed:@"MyClass" owner:self options:nil];
    self.myClass.frame = CGRectMake(X,Y,W,H); //put your values in.
    [self.view addSubview:self.myClass];
}
person sooper    schedule 23.11.2012
comment
Спасибо, попробую, может и у меня сработает. Я бы не хотел привязывать владельца файла к UIViewController, если это возможно, но я собираюсь посмотреть, получу ли я нужную конфигурацию, добавив имя класса в элемент View в IB, а затем свяжу там IBOutlets. - person Cloov; 24.11.2012