Как загрузить UIView с помощью nib-файла, созданного с помощью Interface Builder

241

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

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

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

  1. Создайте объект Question1View как UIViewподкласс, определяя некоторые IBOutlets.
  2. Создайте (используя Интерфейсный Разработчик) 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.

Александр Фарбер
источник

Ответы:

224

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

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

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» даст вам доступ к представлению с вашего кончика!

averydev
источник
4
Будет ли это работать для основного вида контроллера (self.view)? По некоторым причинам мне нужно загрузить основной вид из пера после стандартного метода инициализации контроллера. Причина - сложная структура подклассов
Лукаш
@Lukasz Насколько тебе удается это сделать? Даже я ищу что-то вроде тебя.
Аджай Шарма
Извините, я мог быть толстым, но я смущен в первом пункте. Как создать розетки в подклассе пользовательского представления? Я думал, что розетки были только для UIViewControllers?
Джоуи
1
Как насчет случая, когда это представление появляется в нескольких родительских представлениях? Итак, вы предлагаете редактировать xib для каждого из них вручную? Это очень плохо!!!
user2083364
2
Это работает, только если вы хотите использовать пользовательское перо в одном контроллере представления. В случае, если вы хотите использовать его на разных контроллерах представления, каждый из которых динамически создает экземпляр пользовательского пера, это не будет работать.
thgc
120

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

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

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

Это оно!

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

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

geedubb
источник
2
У меня очень похожая проблема - я просто хочу загрузить UIView из файла XIB. Эти шаги не работают для меня - приложение вылетает с «плохим доступом» вскоре после добавления загруженного представления в качестве подпредставления.
Судья
2
Для iPhone 3.0 используйте objectAtIndex: 0, чтобы получить первый элемент. Это вылетает для меня точно так, как описано Юстиклом. Есть идеи почему?
TBA
1
+1 у меня отлично сработало. Я хотел добавить несколько представлений, у которых был один контроллер представления (я использую сценарий с переворотом, где вращается часть представления) Мне пришлось сделать индекс для массива 0 (iPhone 3.0)
Аран Малхолланд,
42
Это правильный ответ, однако код должен быть изменен таким образом, чтобы он просматривал nibViews и проверял классы на каждом объекте, так что вы, безусловно, получаете правильный объект. objectAtIndex: 1 - опасное предположение.
М. Райан
2
Яскониус прав. Вы должны перебрать массив nibViews следующим образом: gist.github.com/769539
Левиафан,
71

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

Вот код почти на 100% прямо из книги Apress Beginning для 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подкласс с именем Blahnib, 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

Быстрое расширение

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")
    }
}
Дэн Розенстарк
источник
2
Если вы используете, isKindOfвы защищены от изменений структуры Bundle.
Дэн Розенстарк
1
Возможно assert, будет лучшим решением. Таким образом, вы будете только скрывать проблему.
Sulthan
1
@Sulthan assert по своей сути отличается. Мне нужен объект типа Blah, где бы он ни находился в Nib . Мне все равно, будет ли он повышен или понижен в должности. Тем не менее, в ответ на ваш комментарий я добавил утверждение, но оно не в forцикле. Это дополняет это.
Дэн Розенстарк
Вы можете сделать loadFromNib без параметров: + (id) loadFromNib {NSArray * bundle = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass ([self class]) owner: self options: nil]; for (объект id в комплекте) {if ([object isKindOfClass: [self class]]) {return object; }} return nil; }
JVC
22

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

  1. Создайте кастом MyView : UIViewи установите его в качестве « Custom Class » корня UIViewв желаемой XIB ; пользовательский класс

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

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

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

  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..
Франческо Вадикамо
источник
Привет ... ты знаешь, как сделать все это программно, т.е. не с помощью файла XIB вообще?
X-Coder
19

Я бы использовал 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 Note, которые [UINib instantiateWithOwner]возвращают массив, поэтому вам следует использовать элемент, отражающий UIView, который вы хотите использовать повторно. В данном случае это первый элемент.

thgc
источник
1
Разве вы не имеете в виду «customNib» в строке 2 вместо «RankingNib»
Дэнни
11

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

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

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

Марк W
источник
Я читаю книгу Apress «Начало разработки iPhone: изучение iPhone SDK», и они обсуждали этот метод в главе 9: «Контроллеры навигации и табличные представления». Это не казалось слишком сложным.
Линдси Фергюсон
Мне было бы интересно посмотреть, как они говорят, что это сделано. У меня нет копии этой книги в данный момент.
Марк W
10

Вы также можете использовать 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.

Ин
источник
8

Это отличный вопрос (+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, то это круто, но это привело меня туда, где я должен был быть.

Olie
источник
1
Я использовал этот метод, спасибо. Одним из изменений, которые я сделал, чтобы позволить ему поддерживать вложенные представления, было изменение [ self.view insertSubview: msView aboveSubview: mySubview]; to [ mySubview.superview insertSubview: msView вышеуказанный Subview: mySubview];
Джейсон
8

Это то, что должно быть проще. В итоге я расширил 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
Скот
источник
7

В быстром

На самом деле я решил эту проблему, загрузив представление в viewDidLoad в моем CustonViewController, где я хотел использовать это представление так:

myAccessoryView = NSBundle.mainBundle().loadNibNamed("MyAccessoryView", owner: self, options: nil)[0] as! MyAccessoryView

Не загружайте представление в loadView()методе! Метод loadView служит для загрузки представления для вашего пользовательского ViewController.

kalafun
источник
6

Я тоже хотел сделать что-то подобное, вот что я нашел: (SDK 3.1.3)

У меня есть контроллер представления A (принадлежащий контроллеру Nav), который загружает VC B нажатием кнопки:

В AViewController.m

BViewController *bController = [[BViewController alloc] initWithNibName:@"Bnib" bundle:nil];
[self.navigationController pushViewController:bController animated:YES];
[bController release];

Теперь у VC B есть интерфейс от Bnib, но когда нажата кнопка, я хочу перейти в «режим редактирования», в котором отдельный интерфейс от другого пера, но я не хочу новый VC для режима редактирования, Я хочу, чтобы новый кончик был связан с моим существующим B VC.

Итак, в BViewController.m (в методе нажатия кнопки)

NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:@"EditMode" owner:self options:nil];
UIView *theEditView = [nibObjects objectAtIndex:0];
self.editView = theEditView;
[self.view addSubview:theEditView];

Затем нажмите другую кнопку (чтобы выйти из режима редактирования):

[editView removeFromSuperview];

и я вернулся к своему первоначальному Бнибу.

Это прекрасно работает, но учтите, что мой EditMode.nib содержит только 1 объект верхнего уровня, объект UIView. Неважно, установлен ли Владелец файла в этом кончике как BViewController или NSObject по умолчанию, НО убедитесь, что для параметра Просмотр выхода в Владельце файла НЕ установлено ничего. Если это так, то я получаю аварийное завершение exc_bad_access, и xcode переходит к загрузке 6677 кадров стека, показывая неоднократно вызываемый внутренний метод UIView ... так что это выглядит как бесконечный цикл. (Однако в моем оригинальном Bnib установлен Out Outlet)

Надеюсь это поможет.

Brynjar
источник
Читая между строк, это тоже помогло мне.
Питер
Согласно информации в ссылке, вы не должны манипулировать контроллерами представления таким образом.
Т. Маркл
6

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

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;
}
логан
источник
5

Для пользователя Swift с настраиваемой опцией:

  1. Создайте пользовательский UIViewподкласс и файлы xib , которые мы назовем по имени нашего собственного класса: в нашем случае MemeView. Внутри класса Meme View не забудьте определить его как дизайн с помощью@IBDesignable атрибутом до объявления класса
  2. Помните, чтобы установить владельца файла в xib с помощью нашего пользовательского UIViewподкласса на панели Indetity Inspector.

    введите описание изображения здесь

  3. Теперь в xib-файле мы можем построить наш интерфейс, создать ограничения, создать торговые точки, действия и т. Д. введите описание изображения здесь

  4. Нам нужно реализовать несколько методов для нашего пользовательского класса, чтобы открыть XIB после инициализации

    class XibbedView: UIView {

    weak var nibView: UIView!
    
    override convenience init(frame: CGRect) {
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        self.init(nibName: nibName)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    init(nibName: String) {
        super.init(frame: CGRectZero)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    func setUpConstraints() {
        ["V","H"].forEach { (quote) -> () in
            let format = String(format:"\(quote):|[nibView]|")
            addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: ["nibView" : nibView]))
        }
    }
    
    func loadNib(name: String) -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: name, bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
    
        return view
    }

    }

  5. В нашем пользовательском классе мы также можем определить некоторые безупречные свойства, чтобы иметь полный контроль над ними из конструктора интерфейса

    @IBDesignable class MemeView: XibbedView {

    @IBInspectable var memeImage: UIImage = UIImage() {
        didSet {
            imageView.image = memeImage
        }
    }
    @IBInspectable var textColor: UIColor = UIColor.whiteColor() {
        didSet {
            label.textColor = textColor
        }
    }
    @IBInspectable var text: String = "" {
        didSet {
            label.text = text
        }
    }
    @IBInspectable var roundedCorners: Bool = false {
        didSet {
            if roundedCorners {
                layer.cornerRadius = 20.0
                clipsToBounds = true
            }
            else {
                layer.cornerRadius = 0.0
                clipsToBounds = false
            }
        }
    }
    
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var imageView: UIImageView!

}

Несколько примеров:
введите описание изображения здесь введите описание изображения здесь

Если нам нужно добавить больше информации, когда представление отображается внутри раскадровки или другой xib, для этого мы можем реализовать prepareForInterfaceBuilder()этот метод, который будет выполняться только при открытии файла в конструкторе интерфейса. Если вы выполнили все, что я написал, но ничего не работает, это способ отладки представления sigle путем добавления точек останова в его реализации. Вот иерархия просмотров. введите описание изображения здесь
Просмотр hiearchy

Надеюсь, что это помогает полный образец можно скачать здесь

Andrea
источник
Полную статью можно увидеть в моем блоге cloudintouch.it/2016/04/21/designable-xib-in-xcode-hell-yeah, но я уже разместил соответствующую часть.
Андреа
let nib = loadNib(nibName)это экземпляр XibbedView, если вы вызываете `` `addSubview (nib)` ``, то вы добавляете один экземпляр XibbedView в другой экземпляр XibbedView?
Уильям Ху
Я согласен с этим. Когда вы пытаетесь увидеть это из «Debug View Hierarchy», кажется, XibbedView содержит XibbedView. Вы можете видеть это.
Уильям Ху
@WilliamHu Если вы загрузите приведенный пример, вы увидите, что MemeView.xib - это просто экземпляр UIView с кучей подпредставлений, а держателем файла является MemeView, который является подклассом XibbedView. Таким образом, единственный экземпляр XibbedView - это просто MemeView, который загружает XIB-файл, где основной вид - это просто представление
Andrea
Ну ладно, тогда. Я проверю это. Спасибо!
Уильям Ху
4

Я нашел эту запись в блоге Аарона Хиллегаса (автор, инструктор, ниндзя Какао) очень поучительной. Даже если вы не примете его модифицированный подход к загрузке файлов NIB через назначенный инициализатор, вы, вероятно, по крайней мере получите лучшее понимание происходящего процесса. Я использую этот метод в последнее время с большим успехом!

Мельтеми
источник
Ссылка мертва. Я считаю , что в настоящее время находится в bignerdranch.com/blog/...
joelliusp
3

Предыдущий ответ не учитывает изменения в структуре NIB (XIB), которые произошли между 2.0 и 2.1 из iPhone SDK. Пользовательское содержимое теперь начинается с индекса 0 вместо 1.

Вы можете использовать макрос 2.1, который действителен для всех версий 2.1 и выше (это два подчеркивания перед IPHONE:

 // Cited from previous example
 NSArray* nibViews =  [[NSBundle mainBundle] loadNibNamed:@"QPickOneView" owner:self options:nil];
 int startIndex;
 #ifdef __IPHONE_2_1
 startIndex = 0;
 #else
 startIndex = 1;
 #endif
 QPickOneView* myView = [ nibViews objectAtIndex: startIndex];
 myView.question = question;

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

гулянка

Барни Мэттокс
источник
2
Барни: «Предыдущий ответ» не имеет смысла в переполнении стека, потому что отвечает на смену позиции на основе голосования. Лучше обратиться к «ответу Фреда» или «ответу Барни» или «ответу Оли».
Оли
3

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

+ (id) initWithNibName:(NSString *)nibName withSelf:(id)myself {

    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName
                                                    owner:myself options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:[myself class]]) {
            return object;
        }
    }  

    return nil;
} 

Затем я вызываю его из initWithFrameметода моего подкласса так:

- (id)initWithFrame:(CGRect)frame {

    self = [Utilities initWithNibName:@"XIB1" withSelf:self];
    if (self) {
        // Initialization code.
    }
    return self;
}

Опубликовано для общего интереса; если кто-то видит какие-либо проблемы без этого, пожалуйста, дайте мне знать.

MusiGenesis
источник
Я реализовал ваш код, но он продолжает выходить за рамки init. Я сделал ваш статический метод в классе Utilities. Я сделал h / m файлы с именем MyView (подклассы из UIView) и XIB с тем же именем. В IB я установил владельца xib-файлов И представление «MyView». В отладчике это выходит за рамки. Я что-то упускаю в IB?
TigerCoding
Пожалуйста, посмотрите на мой новый вопрос SO здесь: stackoverflow.com/questions/9224857/…
TigerCoding
self = [Утилиты initWithNibName: @ "XIB1" withSelf: self]; Вы передаете себя, когда это еще не установлено. Разве этот код не такой, как этот: self = [Utilities initWithNibName: @ "XIB1" withSelf: nil]; ?
Кен Ван Хойланд
@Javy: мой пример кода здесь может не работать с ARC - я не тестировал его с включенным ARC.
MusiGenesis
3

Вот способ сделать это в Swift (в настоящее время пишу Swift 2.0 в XCode 7 beta 5).

Из вашего UIViewподкласса, который вы задали как «Пользовательский класс» в Интерфейсном Разработчике, создайте метод, подобный этому (мой подкласс называется RecordingFooterView):

class func loadFromNib() -> RecordingFooterView? {
    let nib = UINib(nibName: "RecordingFooterView", bundle: nil)
    let nibObjects = nib.instantiateWithOwner(nil, options: nil)
    if nibObjects.count > 0 {
        let topObject = nibObjects[0]
        return topObject as? RecordingFooterView
    }
    return nil
}

Тогда вы можете просто назвать это так:

let recordingFooterView = RecordingFooterView.loadFromNib()

Johannes
источник
2

Ни один из ответов не объясняет, как создать автономный XIB, который является корнем этого вопроса. Нет Xcode 4опции «Создать новый файл XIB».

Сделать это

1) Choose "New File..."
2) Choose the "User Interface" category under the iOS section
3) Choose the "View" item
4) You will then be prompted to choose an iPhone or iPad format

Это может показаться простым, но это может сэкономить вам несколько минут, ища это, так как слово «XIB» нигде не появляется.

whitneyland
источник
1

@AVeryDev

6) Чтобы прикрепить загруженный вид к виду вашего контроллера:

[self.view addSubview:myViewFromNib];

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

Для пояснения: контроллер представления имеет несколько IBOutlets, некоторые из которых связаны с элементами в исходном файле пера (как обычно), а некоторые связаны с элементами в загруженном пиру. Оба пера имеют один и тот же класс владельца. Загруженный вид накладывается на исходный.

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

Matt
источник
это хорошая вещь, которую нужно остерегаться, но добавление представления в качестве подпредставления автоматически удаляет его из предыдущего родителя, поэтому оно не должно вытекать.
averydev
1

Потратив много часов, я придумал следующее решение. Выполните следующие шаги для создания пользовательских UIView.

1) Создать класс myCustomView, унаследованный от UIView .
введите описание изображения здесь

2) Создайте .xib с именем myCustomView .
введите описание изображения здесь

3) Измените класс UIView внутри вашего .xib файла, назначьте там класс myCustomView .
введите описание изображения здесь

4) Создание IBOutlets
введите описание изображения здесь

5) Загрузите .xib в myCustomView * customView . Используйте следующий пример кода.

myCustomView * myView = [[[NSBundle mainBundle] loadNibNamed:@"myCustomView" owner:self options:nil] objectAtIndex:0];
[myView.description setText:@"description"];

Примечание: для тех, кто все еще сталкивается с проблемой, можете прокомментировать, я предоставлю им ссылку примера проекта с myCustomView

Аамир
источник
1

Кратчайшая версия:

RSFavoritePlaceholderView *favoritePlaceholderView = [[[NSBundle mainBundle] loadNibNamed:@"RSFavoritePlaceholderView" owner:self options:nil] firstObject];
Денис Кутлубаев
источник
0

У меня есть соглашение о присвоении имен Xibs с представлениями в них так же, как представление. То же самое, что можно было бы сделать для контроллера представления. Тогда мне не нужно записывать имена классов в коде. Я загружаю UIView из файла пера с тем же именем.

Пример для класса под названием MyView.

  • Создайте файл пера MyView.xib в Интерфейсном Разработчике
  • В Интерфейсном Разработчике добавьте UIView. Установите его класс в MyView. Настройте по своему вкусу, подключите переменные экземпляра MyView к подпредставлениям, к которым вы, возможно, захотите обратиться позже.
  • В своем коде создайте новый MyView следующим образом:

    MyView * myView = [MyView nib_viewFromNibWithOwner: owner];

Вот категория для этого:

@implementation UIView (nib)

+ (id) nib_viewFromNib {
    return [self nib_viewFromNibWithOwner:nil];
}

+ (id) nib_viewFromNibWithOwner:(id)owner {
    NSString *className = NSStringFromClass([self class]);
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:className owner:owner options:nil];
    UIView *view = nil;
    for(UIView *v in nib) {
        if ([v isKindOfClass:[self class]]) {
            view = v;
            break;
        }
    }
    assert(view != nil && "View for class not found in nib file");
    [view nib_viewDidLoad];
    return view;
}

// override this to do custom setup
-(void)nib_viewDidLoad {

}

Затем я бы связывал кнопки с действиями используемого мной контроллера и устанавливал метки на ярлыках, используя выходы в моем подклассе пользовательского представления.

n13
источник
0

Чтобы программно загрузить представление из nib / xib в Swift 4:

// Load a view from a Nib given a placeholder view subclass
//      Placeholder is an instance of the view to load.  Placeholder is discarded.
//      If no name is provided, the Nib name is the same as the subclass type name
//
public func loadViewFromNib<T>(placeholder placeholderView: T, name givenNibName: String? = nil) -> T {

    let nib = loadNib(givenNibName, placeholder: placeholderView)
    return instantiateView(fromNib: nib, placeholder: placeholderView)
}

// Step 1: Returns a Nib
//
public func loadNib<T>(_ givenNibName: String? = nil, placeholder placeholderView: T) -> UINib {
    //1. Load and unarchive nib file
    let nibName = givenNibName ?? String(describing: type(of: placeholderView))

    let nib = UINib(nibName: nibName, bundle: Bundle.main)
    return nib
}

// Step 2: Instantiate a view given a nib
//
public func instantiateView<T>(fromNib nib: UINib, placeholder placeholderView: T) -> T {
    //1. Get top level objects
    let topLevelObjects = nib.instantiate(withOwner: placeholderView, options: nil)

    //2. Have at least one top level object
    guard let firstObject = topLevelObjects.first else {
        fatalError("\(#function): no top level objects in nib")
    }

    //3. Return instantiated view, placeholderView is not used
    let instantiatedView = firstObject as! T
    return instantiatedView
}
Анг тис
источник
-1

В итоге я добавил категорию в UIView для этого:

 #import "UIViewNibLoading.h"

 @implementation UIView (UIViewNibLoading)

 + (id) loadNibNamed:(NSString *) nibName {
    return [UIView loadNibNamed:nibName fromBundle:[NSBundle mainBundle] retainingObjectWithTag:1];
 }

 + (id) loadNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag {
    NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil];
    if(!nib) return nil;
    UIView * target = nil;
    for(UIView * view in nib) {
        if(view.tag == tag) {
            target = [view retain];
            break;
        }
    }
    if(target && [target respondsToSelector:@selector(viewDidLoad)]) {
        [target performSelector:@selector(viewDidLoad)];
    }
    return [target autorelease];
 }

 @end

объяснение здесь: viewcontroller меньше загружает представление в ios & mac

gngrwzrd
источник
Хмм. Что с тегом?
13
знать, какой объект верхнего уровня сохранить. в то время, когда это было необходимо, но вы, вероятно, могли бы сейчас извлечь остатки, чтобы соответствовать ARC.
gngrwzrd