Як завантажити UIView за допомогою файлу nib, створеного за допомогою Interface Builder


241

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

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

Я це роблю:

  1. Створіть об’єкт Question1View як UIViewпідклас, визначивши деякі IBOutlets.
  2. Створіть (використовуючи програму Interface Builder) Question1View.xib (ТУТ, ЩО МОЯ ПРОБЛЕМА ПРОБЛЕМНО ). Я поставив як UIViewControllerі UIViewбути класу Question1View.
  3. Я пов'язую торгові точки з компонентом подання (за допомогою ІБ).
  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 ) Створіть спеціальний підклас Перегляд з будь-якими торговими точками, до яких ви хочете отримати доступ пізніше. --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" надасть вам доступ до перегляду від вашої нитки!


4
Чи буде це працювати для головного контролера (self.view)? З якихось причин мені потрібно завантажити основний вигляд з ручки після стандартного методу init. Причина - складна структура підкласифікації
Лукаш

@Lukasz Скільки вам це вдається? Навіть я шукаю щось подібне до вас.
Аджай Шарма

Вибачте, що я можу бути товстим, але я з першим моментом заплутався. Як створити торгові точки в підкласі перегляду користувачем? Я думав, торгові точки призначені лише для UIViewControllers?
jowie

1
Що з випадком, коли цей вид відображається на кількох батьківських поданнях? Тож чи пропонуєте ви редагувати xib для кожного з них вручну? Це ТОГО БАД !!!
користувач2083364

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 може відображати подання чи інший вид, залежно від даних у пам'яті, і "батьківському" екрану не потрібно турбуватися з цією логікою.


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
leviathan

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підклас під назвою 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

Швидке розширення

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.
Dan Rosenstark

1
assertБ , ймовірно , кращим рішенням. Таким чином ви лише приховете проблему.
Султан

1
@ Султан твердження суттєво відрізняється. Я хочу, щоб об'єкт типу Blah був там, де він сидить у Nib . Мені не байдуже, чи її підвищують чи знижують. Однак у відповідь на ваш коментар я додав твердження, але це не замість forциклу. Це доповнює його.
Дан Розенстарк

Ви можете робити loadFromNib без параметрів: + (id) loadFromNib {NSArray * bundle = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass ([самоклас]) Власник: self options: nil]; for (id-об’єкт у пакеті) {if ([object isKindOfClass: [self class]]) {return object; }} повернути нуль; }
JVC

22

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

  1. Створіть спеціальний MyView : UIViewта встановіть його як " Спеціальний клас " кореня UIViewв потрібному XIB ; нестандартний клас

  2. Створіть усі торгові точки, які вам потрібні MyView(зробіть це зараз, оскільки після пункту 3 ІБ запропонує вам підключити розетки до 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 Зверніть увагу, що [UINib instantiateWithOwner]повертає масив, тому ви повинні використовувати елемент, що відображає UIView, який ви хочете повторно використовувати. У цьому випадку це перший елемент.


1
Ви не маєте на увазі "customNib" у другому рядку замість "rankingNib"
Danny

11

Вам не слід встановлювати клас контролера перегляду як підклас UIViewв Interface Builder. Це, безумовно, принаймні частина вашої проблеми. Залиште це як будь-який UIViewControllerпідклас цього класу або якийсь інший спеціальний клас у вас.

Що стосується завантаження тільки виду з XIb, я був в припущенні , що у вас були мати якесь - то контролер уявлення (навіть якщо він не поширюється UIViewController, що може бути дуже важким для ваших потреб) встановлюються в якості файлу власника в інтерфейсі Builder, якщо ви хочете використовувати його для визначення вашого інтерфейсу. Я також зробив невелике дослідження, щоб підтвердити це. Це тому, що в іншому випадку не було б можливості отримати доступ до будь-якого з елементів інтерфейсу UIView, а також не було б способу, щоб ваші власні методи в коді були ініційовані подіями.

Якщо ви використовуєте UIViewControllerяк власник свого файлу для перегляду, ви можете просто initWithNibName:bundle:завантажити його та повернути об’єкт контролера перегляду. У IB переконайтеся, що ви встановили viewрозетку для подання за допомогою інтерфейсу в xib. Якщо ви використовуєте який - або інший тип об'єкту в вашому файлі власника, ви повинні будете використовувати NSBundle«s loadNibNamed:owner:options:метод , щоб завантажити перо, передаючи екземпляр файлу власника до методу. Усі його властивості будуть встановлені належним чином відповідно до торгових точок, визначених у IB.


Я читаю книгу Apress, "Початок розробки iPhone: Дослідження iPhone SDK", і вони обговорили цей метод у розділі 9: Контролери навігації та Перегляд таблиць. Це не здавалося надто складним.
Лінді Фергюсон

Мені буде цікаво подивитися, як вони кажуть, що це робиться. Наразі копії цієї книги у мене немає.
Marc 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's за бажанням
  • Ще в 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методів s, слід встановити viewвластивість у .xib-файлі MySubview. Інакше він повертається як звичайний старий UIView.

Якщо є спосіб завантажити mySubviewбезпосередньо з власного xib, це би рок, але це дістало мене там, де я мав бути.


1
Я використовував цей метод, дякую. Однією з змін, які я вніс, щоб дозволити йому підтримувати вкладені погляди, було змінити [ self.view insertSubview: msView aboveSubview: mySubview]; до [ mySubview.superview insertSubview: msView aboveSubview: mySubview];
Джейсон

8

Це щось, що повинно бути простіше. Я закінчив розширення UIViewControllerта додавання loadNib:inPlaceholder:селектора. Тепер я можу сказати

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

Ось код для категорії (він робить той же ригамарол, як описано у Gonso):

@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.


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];

і я повернувся до свого оригінального Bnib.

Це прекрасно працює, але зауважте, що мій EditMode.nib має лише 1 obj верхнього рівня в ньому, UIView obj. Не має значення, встановлено, що власник файлу в цій ручці встановлений як BViewController або NSObject за замовчуванням, Але переконайтесь, що в Outlet View у власнику файлу НЕ встановлено нічого. Якщо це так, то я отримую збої exc_bad_access і xcode продовжує завантажувати 6677 кадри стека, показуючи внутрішній метод UIView, неодноразово викликаний ... так виглядає як нескінченний цикл. (Офіс "Out Outlet IS" встановлений у моєму оригінальному Bnib)

Сподіваюсь, це допомагає.


Читання між рядків, і це мені допомогло.
петерт

Відповідно до інформації за посиланням, ви не повинні маніпулювати контролерами перегляду таким чином.
Т. Маркл

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 пам’ятайте, щоб визначити його як @IBDesignableатрибут перед оголошенням класу
  2. Не забудьте встановити власника файлу в xib за допомогою нашого спеціального UIViewпідкласу на панелі інспектора індетіти

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

  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()цей метод буде виконуватися лише під час відкриття файлу в програмі інтерфейсу. Якщо ви зробили все, що я написав, але нічого не працює, це спосіб налагодити перегляд сиглів, додавши точки прориву в його реалізації. Ось ієрархія поглядів. введіть тут опис зображення
Переглянути hiearchy

Сподіваюся, що це допоможе повний зразок можна завантажити тут


Повну статтю можна побачити з мого блогу cloudintouch.it/2016/04/21/designable-xib-in-xcode-hell-y yes, але я вже розмістив відповідну частину.
Андреа

let nib = loadNib(nibName)це екземпляр XibbedView, якщо ви викликаєте `` `addSubview (nib)` ``, тоді ви додаєте один екземпляр XibbedView в інший екземпляр XibbedView?
Вільям Ху

Я згоден з цього приводу. Коли ви намагаєтесь побачити це з "Ієрархії перегляду налагодження", схоже, що XibbedView містить XibbedView. Ви можете це побачити.
Вільям Хю

@WilliamHu Якщо ви завантажуєте приклад подання, ви можете бачити, що MemeView.xib - це лише екземпляр UIView з купою підзаглядів, власник файлу - MemeView, що є підкласом XibbedView. Таким чином, єдиний екземпляр XibbedView - це лише MemeView, який завантажує файл xib, де основним видом є лише вид
Андреа,

Так добре, значить. Я тестую це. Дякую!
Вільям Ху,

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
Барні: "Попередня відповідь" є безглуздою для Stacka Overflow, оскільки відповіді змінюють позицію на основі голосування. Краще посилатися на "відповідь Фреда" або "відповідь Барні" або "відповідь Олі".
Олі

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

Опубліковано для загального інтересу; якщо хтось бачить якісь проблеми, не роблячи це таким чином, будь ласка, дайте мені знати.


Я реалізував ваш код, але він не виходить із програми init. Я зробив ваш статичний метод у класі Utilities. Я створив ч / м файли під назвою MyView (підклас від UIView) і xib з таким же ім'ям. У IB я встановив власника файлів xib І вигляд на "MyView". У налагоджувачі це поза сферою. Я щось пропускаю в IB?
TigerCoding

Будь ласка , зверніть увагу на мій новий SO питання тут: stackoverflow.com/questions/9224857 / ...
TigerCoding

self = [Утиліти initWithNibName: @ "XIB1" withSelf: self]; Ви проходите самоврядування, коли воно ще не встановлено. Чи не той код такий, як цей: self = [Утиліти initWithNibName: @ "XIB1" withSelf: nil]; ?
Кен Ван Хойландт

@Javy: мій зразок коду тут може не працювати з ARC - я не перевіряв його з включеною ARC.
MusiGenesis

3

Ось спосіб це зробити в Swift (зараз пишуть Swift 2.0 в XCode 7 beta 5).

З UIViewпідкласу, який ви встановили як "Спеціальний клас" у програмі Interface Builder, створіть такий метод (мій підклас називається 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()


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" ніде не з'являється.


1

@AVeryDev

6) Щоб приєднати завантажений вид до подання контролера перегляду:

[self.view addSubview:myViewFromNib];

Імовірно, його потрібно видалити з виду, щоб уникнути витоку пам'яті.

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

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


добре спостерігати, але додавання подання як підвід, автоматично видаляє його з попереднього батьківського, тому воно не повинно протікати.
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 з файлу nib з тим самим іменем.

Приклад для класу під назвою MyView.

  • Створіть файл nib під назвою MyView.xib в Interface Builder
  • В Інтерфейс Builder додайте UIView. Встановіть його клас на MyView. Налаштуйте вміст свого серця, підключіть змінні екземпляри MyView до підзаписів, до яких ви хочете отримати доступ пізніше.
  • У своєму коді створіть новий MyView так:

    MyView * myView = [MyView nib_viewFromNibWithOwner: власник];

Ось категорія для цього:

@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 {

}

Тоді я б з'єднав кнопки з діями контролера, який я використовую, і встановив речі на мітках, використовуючи розетки в моєму підкласі перегляду.


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


Грн. Що з тегом?
n13

знати, який об’єкт верхнього рівня потрібно зберегти. на той час це було необхідно, але ви, ймовірно, могли витягнути упори, щоб відповідати ARC зараз.
gngrwzrd
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.