Как загрузить UIView с помощью nib-файла, созданного с помощью Interface Builder - PullRequest
237 голосов
/ 14 мая 2009

Я пытаюсь сделать что-то немного сложное, но то, что должно быть возможным. Так что это вызов для всех вас, экспертов (на этом форуме собрались многие из вас, ребята :)).

Я создаю «компонент» вопросника, который я хочу загрузить на NavigationContoller (мой QuestionManagerViewController). «Компонент» - это «пустой» UIViewController, который может загружать различные представления в зависимости от вопроса, на который необходимо ответить.

Я делаю это так:

  1. Создание объекта Question1View в качестве подкласса UIView, определяющего некоторый IBOutlets.
  2. Создайте (используя Interface Builder) Question1View.xib (ЗДЕСЬ, ГДЕ МОЯ ПРОБЛЕМА, ВОЗМОЖНО, ). Я установил UIViewController и UIView для класса Question1View.
  3. Я связываю торговые точки с компонентом вида (используя IB).
  4. Я переопределяю initWithNib моего QuestionManagerViewController, чтобы он выглядел следующим образом:

    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) {
            // Custom initialization
        }
        return self;
    }
    

Когда я запускаю код, я получаю эту ошибку:

2009-05-14 15: 05: 37.152 iMobiDines [17148: 20b] *** Завершение работы приложения из-за необработанного исключения «NSInternalInconsistencyException», причина: «-[UIViewController _loadViewFromNibNamed:bundle:] загрузил перо« Question1View », но выход из представления не был установлен. '

Я уверен, что есть способ загрузить представление, используя nib-файл, без необходимости создавать класс viewController.

Ответы [ 24 ]

223 голосов
/ 29 октября 2010

Существует также более простой способ доступа к представлению, вместо того, чтобы обрабатывать перо в виде массива.

1 ) Создайте пользовательский подкласс View с любыми точками, к которым вы хотите иметь доступ позже. --MyView

2 ) в UIViewController, который вы хотите загрузить и обработать пером, создайте свойство IBOutlet, которое будет содержать представление загруженного кончика, например

в MyViewController (подкласс UIViewController)

  @property (nonatomic, retain) IBOutlet UIView *myViewFromNib;

(не забудьте синтезировать его и выпустить в файле .m)

3) откройте перо (мы назовем его myViewNib.xib) в IB, установите для владельца файла значение MyViewController

4) теперь подключите выход MyViewFromNib владельца файла к основному виду в кончике.

5) Теперь в MyViewController напишите следующую строку:

[[NSBundle mainBundle] loadNibNamed:@"myViewNib" owner:self options:nil];

Теперь, как только вы сделаете это, вызов вашего свойства self.myViewFromNib предоставит вам доступ к представлению с вашего кончика!

119 голосов
/ 15 мая 2009

Спасибо всем. Я нашел способ сделать то, что хотел.

  1. Создайте UIView с IBOutlet s, которые вам нужны.
  2. Создайте xib в IB, создайте его по своему вкусу и свяжите с ним так: владелец файла принадлежит к классу UIViewController (нет пользовательского подкласса, кроме «реального»). Представление «Владелец файла» подключено к основному представлению, а его класс объявлен как класс из шага 1).
  3. Соедините ваши элементы управления с IBOutlet s.
  4. DynamicViewController может запустить свою логику, чтобы решить, какое представление / xib загрузить. Как только он принял решение, в методе loadView поместите что-то вроде этого:

    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView"
                                                      owner:self
                                                    options:nil];
    
    QPickOneView* myView = [ nibViews objectAtIndex: 1];
    
    myView.question = question;
    

Вот и все!

Метод loadNibNamed основного пакета позаботится об инициализации представления и создании соединений.

Теперь ViewController может отображать то или иное представление в зависимости от данных в памяти, и «родительскому» экрану не нужно беспокоиться об этой логике.

69 голосов
/ 07 июля 2010

Я не уверен, о чем идет речь в некоторых ответах, но мне нужно разместить этот ответ здесь, когда я буду искать в Google в следующий раз. Ключевые слова: «Как загрузить UIView из пера» или «Как загрузить UIView из NSBundle.»

Вот код почти на 100% прямо из книги Apress Начиная с iPhone 3 (стр. 247, «Использование новой ячейки табличного представления»):

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:@"Blah"
                                                 owner:self options:nil];
    Blah *blah;
    for (id object in bundle) {
        if ([object isKindOfClass:[Blah class]]) {
            blah = (Blah *)object;
            break;
        }
    }   
    assert(blah != nil && "blah can't be nil");
    [self.view addSubview: blah];
} 

Предполагается, что у вас есть подкласс UIView с именем Blah, перо с именем Blah, которое содержит UIView, класс которого установлен на Blah.


Категория: NSObject + LoadFromNib

#import "NSObject+LoadFromNib.h"

@implementation NSObject (LoadFromNib)

+ (id)loadFromNib:(NSString *)name classToLoad:(Class)classToLoad {
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:classToLoad]) {
            return object;
        }
    }
    return nil;
}

@end

Swift Extension

extension UIView {
    class func loadFromNib<T>(withName nibName: String) -> T? {
        let nib  = UINib.init(nibName: nibName, bundle: nil)
        let nibObjects = nib.instantiate(withOwner: nil, options: nil)
        for object in nibObjects {
            if let result = object as? T {
                return result
            }
        }
        return nil
    }
}

И пример использования:

class SomeView: UIView {
    class func loadFromNib() -> SomeView? {
        return self.loadFromNib(withName: "SomeView")
    }
}
22 голосов
/ 24 сентября 2012

Для всех тех, кому необходимо управлять более чем одним экземпляром пользовательского представления, а именно Outlet Collection , я объединил и настроил ответы @Gonso, @AVeryDev и @Olie следующим образом:

  1. Создайте пользовательский MyView : UIView и установите его как " Пользовательский класс " для корня UIView в желаемом XIB ; custom class

  2. Создайте все розетки , которые вам нужны, в MyView (сделайте это сейчас, потому что после пункта 3 IB предложит вам подключать розетки к UIViewController, а не к пользовательскому представлению, как хочу); custom class outlet

  3. Установите UIViewController как « Владелец файла » пользовательского представления XIB ; enter image description here

  4. В UIViewController добавьте новые UIViews для каждого необходимого экземпляра MyView и подключите их к UIViewController, создав Outlet Collection : эти представления будут действовать как представления «обертки» для экземпляров пользовательских представлений; enter image description here

  5. Наконец, в viewDidLoad вашего UIViewController добавьте следующие строки:

NSArray *bundleObjects;
MyView *currView;
NSMutableArray *myViews = [NSMutableArray arrayWithCapacity:myWrapperViews.count];
for (UIView *currWrapperView in myWrapperViews) {
    bundleObjects = [[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil];
    for (id object in bundleObjects) {
        if ([object isKindOfClass:[MyView class]]){
            currView = (MyView *)object;
            break;
        }
    }

    [currView.myLabel setText:@"myText"];
    [currView.myButton setTitle:@"myTitle" forState:UIControlStateNormal];
    //...

    [currWrapperView addSubview:currView];
    [myViews addObject:currView];
}
//self.myViews = myViews; if need to access them later..
18 голосов
/ 24 февраля 2014

Я бы использовал UINib для создания экземпляра пользовательского UIView для повторного использования

UINib *customNib = [UINib nibWithNibName:@"MyCustomView" bundle:nil];
MyCustomViewClass *customView = [[customNib instantiateWithOwner:self options:nil] objectAtIndex:0];
[self.view addSubview:customView];

В этом случае необходимы файлы MyCustomView.xib , MyCustomViewClass.h и MyCustomViewClass.m Обратите внимание, что [UINib instantiateWithOwner] возвращает массив, поэтому вы должны использовать элемент, который отражает UIView, который вы хотите использовать повторно. В данном случае это первый элемент.

10 голосов
/ 15 октября 2011

Вы также можете использовать initWithNibName UIViewController вместо loadNibNamed. Это проще, я нахожу.

UIViewController *aViewController = [[UIViewController alloc] initWithNibName:@"MySubView" bundle:nil];
[self.subview addSubview:aViewController.view];
[aViewController release];  // release the VC

Теперь вам просто нужно создать MySubView.xib и MySubView.h / m. В MySubView.xib установите для класса «Владелец файла» значение UIViewController, а для класса представления - «MySubView».

Вы можете позиционировать и размер subview, используя родительский файл xib.

10 голосов
/ 14 мая 2009

Вы не должны устанавливать класс вашего контроллера представления как подкласс UIView в Интерфейсном Разработчике. Это определенно, по крайней мере, часть вашей проблемы. Оставьте это как UIViewController, некоторый его подкласс или какой-либо другой пользовательский класс, который у вас есть.

Что касается загрузки только вида из xib, я предполагал, что у вас было , чтобы иметь какой-то контроллер вида (даже если он не расширяет UIViewController, что может быть слишком Тяжеловес для ваших нужд) установите в качестве владельца файла в Интерфейсном Разработчике, если вы хотите использовать его для определения вашего интерфейса. Я провёл небольшое исследование, чтобы подтвердить это. Это связано с тем, что в противном случае не было бы никакого способа получить доступ к любому из элементов интерфейса в UIView и не было бы способа инициировать ваши собственные методы в коде событиями.

Если вы используете UIViewController в качестве владельца файла для ваших представлений, вы можете просто использовать initWithNibName:bundle:, чтобы загрузить его и вернуть объект контроллера представления. В IB убедитесь, что вы установили выход view в вид с вашим интерфейсом в xib. Если вы используете какой-либо другой тип объекта в качестве владельца файла, вам нужно будет использовать метод NSBundle loadNibNamed:owner:options: для загрузки пера, передавая экземпляр владельца файла методу. Все его свойства будут установлены в соответствии с точками, которые вы определили в IB.

8 голосов
/ 30 августа 2011

Это должно быть проще. В итоге я расширил UIViewController и добавил селектор loadNib:inPlaceholder:. Теперь я могу сказать

self.mySubview = (MyView *)[self loadNib:@"MyView" inPlaceholder:mySubview];

Вот код для категории (она делает то же самое ригамароле, как описано Гонсо):

@interface UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName;
- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder;

@end

@implementation UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName
{
  NSArray *xib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil]; 
  for (id view in xib) { // have to iterate; index varies
    if ([view isKindOfClass:[UIView class]]) return view;
  }
  return nil;
}

- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder
{
  UIView *nibView = [self viewFromNib:nibName];
  [nibView setFrame:placeholder.frame];
  [self.view insertSubview:nibView aboveSubview:placeholder];
  [placeholder removeFromSuperview];
  return nibView;
}

@end
8 голосов
/ 09 июля 2011

Это отличный вопрос (+1), и ответы были почти полезными;) Извините, ребята, но у меня было чертовски потраченное время, хотя Gonso и AVeryDev давали хорошие советы. Надеюсь, этот ответ поможет другим.

MyVC - это контроллер вида, содержащий все эти вещи.

MySubview - это представление, которое мы хотим загрузить с xib

  • В MyVC.xib создайте представление типа MySubView, которое имеет правильный размер и форму и расположено там, где вам нужно.
  • В MyVC.h есть

    IBOutlet MySubview *mySubView
    // ...
    @property (nonatomic, retain) MySubview *mySubview;
    
  • В MyVC.m @synthesize mySubView; и не забудьте выпустить его в dealloc.

  • В MySubview.h есть выход / свойство для UIView *view (может быть ненужным, но для меня это сработало.) Синтезируйте и отпустите его в .m
  • В MySubview.xib
    • установите тип владельца файла на MySubview и свяжите свойство представления с вашим представлением.
    • Выложите все биты и подключитесь к IBOutlet как требуется
  • Назад в MyVC.m, есть

    NSArray *xibviews = [[NSBundle mainBundle] loadNibNamed: @"MySubview" owner: mySubview options: NULL];
    MySubview *msView = [xibviews objectAtIndex: 0];
    msView.frame = mySubview.frame;
    UIView *oldView = mySubview;
    // Too simple: [self.view insertSubview: msView aboveSubview: mySubview];
    [[mySubview superview] insertSubview: msView aboveSubview: mySubview]; // allows nesting
    self.mySubview = msView;
    [oldCBView removeFromSuperview];
    

Хитрость для меня заключалась в том, что подсказки в других ответах загружали мое представление из xib, но НЕ заменяли представление в MyVC (да!) - мне пришлось поменять его самостоятельно.

Кроме того, чтобы получить доступ к методам mySubview, для свойства view в файле .xib должно быть установлено значение MySubview. В противном случае он возвращается как обычный UIView.

Если есть способ загрузить mySubview непосредственно из его собственной xib, это было бы круто, но это привело меня туда, где я должен был быть.

6 голосов
/ 25 сентября 2014

Я сделал категорию, которая мне нравится:

UIView+NibInitializer.h

#import <UIKit/UIKit.h>

@interface UIView (NibInitializer)
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil;
@end

UIView+NibInitializer.m

#import "UIView+NibInitializer.h"

@implementation UIView (NibInitializer)

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    if (!nibNameOrNil) {
        nibNameOrNil = NSStringFromClass([self class]);
    }
    NSArray *viewsInNib = [[NSBundle mainBundle] loadNibNamed:nibNameOrNil
                                                        owner:self
                                                      options:nil];
    for (id view in viewsInNib) {
        if ([view isKindOfClass:[self class]]) {
            self = view;
            break;
        }
    }
    return self;
}

@end

Тогда звоните так:

MyCustomView *myCustomView = [[MyCustomView alloc] initWithNibNamed:nil];

Используйте имя пера, если ваше перо имеет имя, отличное от имени вашего класса.

Чтобы переопределить его в ваших подклассах для дополнительного поведения, оно может выглядеть так:

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    self = [super initWithNibNamed:nibNameOrNil];
    if (self) {
        self.layer.cornerRadius = CGRectGetHeight(self.bounds) / 2.0;
    }
    return self;
}
...