программная загрузка объекта из подкласса NSView из nib

#objective-c #cocoa #macos

#objective-c #cocoa #macos

Вопрос:

Как мне правильно загрузить объект, который является подклассом NSView, используя Xib?

Я хочу, чтобы он загружался динамически, а не с самого начала, поэтому я создал MyView.Xib из MyDocument.я сделал:

 MyView *myView = [[MyView alloc] initWithFrame:...];
[NSBundle loadNibNamed:@"MyView" owner:myView];
[someview addSubview:myView];
...
  

и фон в порядке (он вызывает drawrect: и он отрисовывается, как ожидалось)
но все кнопки, которые я разместил на xib, не появятся.
Я проверил, и они загружены, НО их superview — это не тот же объект, что myView .

Почему это? Я думаю, что мне чего-то не хватает в Xib, но я не знаю точно, чего. Другими словами: как мне убедиться, что корневое представление в моем xib является тем же объектом, что и владелец файла?

Хотелось бы, чтобы для mac было что-то подобное:

 NSArray* nibViews =  [[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil]; //in iOS this will load the xib
MyView* myView = [ nibViews objectAtIndex:1];
[someview addSubview:myView];
...
  

Заранее спасибо.

Редактировать

Я думаю, что я понял причину проблемы … (??)

В моем классе View у меня есть различные IBOutlet, которые правильно подключены в IB, поэтому они загружаются просто отлично (я могу сослаться на них). Однако для вида сверху нет IBOutlet. Таким образом, когда NSBundle загружает nib, вид сверху присваивается какому-либо другому объекту. Я думал, что это произойдет, если я установлю вид сверху в IB из class: MyView и вставлю myView в качестве владельца в [NSBundle loadNibNamed:@"MyView" owner:myView]; , но, похоже, это не так. Что я делаю не так?

Комментарии:

1. Попробуйте переместить их вперед, возможно, это отрисовывает его сзади и его нельзя увидеть из-за фона

2. Я только что попробовал … этого не происходит; (

3. В вашем файле nib есть представление верхнего уровня, класс которого MyView ? Есть ли другие объекты верхнего уровня в файле nib? И каков класс владельца файла?

4. В моем nib: класс владельца файла — это MyView вид сверху в нем в настоящее время относится к классу NSView , но я тоже пробовал MyView . Ни то, ни другое не сработало

Ответ №1:

Обратите внимание, что файл nib содержит:

  • Один или несколько объектов верхнего уровня. Например, экземпляр MyView ;
  • Заполнитель владельца файла. Это объект, который существовал до загрузки файла nib. Поскольку он существует до загрузки файла nib, он не может совпадать с представлением в файле nib.

Сценарий 1: NSNib

Давайте рассмотрим, что в вашем файле nib есть только один объект верхнего уровня, классом которого является MyView , а классом владельца файла является MyAppDelegate . В этом случае, учитывая, что файл nib загружается в метод экземпляра MyAppDelegate :

 NSNib *nib = [[[NSNib alloc] initWithNibNamed:@"MyView" bundle:nil] autorelease];
NSArray *topLevelObjects;
if (! [nib instantiateWithOwner:self topLevelObjects:amp;topLevelObjects]) // error

MyView *myView = nil;
for (id topLevelObject in topLevelObjects) {
    if ([topLevelObject isKindOfClass:[MyView class]) {
        myView = topLevelObject;
        break;
    }
}

// At this point myView is either nil or points to an instance
// of MyView that is owned by this code
  

Похоже, что первым аргументом -[NSNib instantiateNibWithOwner:topLevelObjects:] может быть nil , поэтому вам не нужно указывать владельца, поскольку кажется, что вы не заинтересованы в его наличии:

 if (! [nib instantiateWithOwner:nil topLevelObjects:amp;topLevelObjects]) // error
  

Хотя это работает, я бы не стал на это полагаться, поскольку это не документировано.

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

Сценарий 2: NSViewController

Cocoa предоставляет NSViewController для управления представлениями; обычно представления загружаются из файла nib. Вы должны создать подкласс NSViewController , содержащий любые необходимые выходы. При редактировании файла nib установите view выход и любые другие выходы, которые вы, возможно, определили. Вы также должны установить владельца файла nib таким образом, чтобы его классом был этот подкласс NSViewController . Затем:

 MyViewController *myViewController = [[MyViewController alloc] initWithNibName:@"MyView" bundle:nil];
  

NSViewController автоматически загружает файл nib и устанавливает соответствующие выходы при его отправке -view . В качестве альтернативы, вы можете принудительно загрузить файл nib, отправив его -loadView .

Комментарии:

1. В моем nib есть только один объект верхнего уровня, класс которого «MyView», но владельцем файла также является «MyView», возможно ли это?.

2. Это возможно, если у вас есть другой экземпляр MyView , который был создан до загрузки файла nib. Обратите внимание, что это подразумевает, что этот другой экземпляр либо был создан программно, либо загружен из другого файла nib, отличного от MyView.nib.

3. У меня есть экземпляр MyView * MyView, который был создан с использованием [[MyView alloc] initWithFrame:]; Я использую это в [NSBundle loadNib:@»MyView» владелец: MyView]; Я все еще не понимаю, почему это не работает; (

4. @nacho4d Конечно, но он не будет таким же, как загруженный из файла nib. Это будет другой вид, поэтому вы сталкиваетесь с проблемами, описанными в вашем вопросе. Как я написал в своем ответе, ‘Поскольку он (владелец файла) существует до загрузки файла nib, он не может совпадать с представлением в файле nib’.

5. Да, часто используется подкласс UIViewController, но иногда я делаю его подклассом UIView, потому что я могу загрузить этот конкретный UIView, когда это необходимо, а не точно, когда загружен контроллер представления. (Потому что обычно это что-то, что не всегда видно, или что-то дорогостоящее для загрузки с самого начала) Я хотел бы сделать то же самое в этом приложении для Mac, которое я пытаюсь сделать.

Ответ №2:

Вы можете обратиться к этой категории

https://github.com/peterpaulis/NSView-NibLoading-/tree/master

   (NSView *)loadWithNibNamed:(NSString *)nibNamed owner:(id)owner class:(Class)loadClass {

    NSNib * nib = [[NSNib alloc] initWithNibNamed:nibNamed bundle:nil];

    NSArray * objects;
    if (![nib instantiateWithOwner:owner topLevelObjects:amp;objects]) {
        NSLog(@"Couldn't load nib named %@", nibNamed);
        return nil;
    }

    for (id object in objects) {
        if ([object isKindOfClass:loadClass]) {
            return object;
        }
    }
    return nil;
}
  

Ответ №3:

Я нашел ответ от @Bavarious очень полезным, но с моей стороны все еще требовалась некоторая доработка, чтобы заставить его работать для моего варианта использования — загрузить одно из нескольких определений представления xib в существующее представление «контейнер». Вот мой рабочий процесс:

1) В .xib создайте представление в IB, как и ожидалось.

2) НЕ добавляйте объект для ViewController нового представления, а скорее

3) Установите для владельца файла .xib значение ViewController нового представления

4) Подключайте любые выходы и действия к владельцу файла, и, в частности, .view

5) Вызовите loadInspector (см. Ниже).

 enum InspectorType {
    case None, ItemEditor, HTMLEditor

    var vc: NSViewController? {
        switch self {
        case .ItemEditor:
            return ItemEditorViewController(nibName: "ItemEditor", bundle: nil)
        case .HTMLEditor:
            return HTMLEditorViewController(nibName: "HTMLEditor", bundle: nil)
        case .None:
            return nil
        }
    }
}

class InspectorContainerView: NSView {

    func loadInspector(inspector: InspectorType) -> NSViewController? {
        self.subviews = [] // Get rid of existing subviews.
        if let vc = inspector.vc {
            vc.loadView()
            self.addSubview(vc.view)
            return vc
        }
        Swift.print("VC NOT loaded from (inspector)")
        return nil
    }
}
  

Ответ №4:

Вот способ написать подкласс NSView, чтобы само представление полностью загружалось из отдельного xib и все было настроено правильно. Это зависит от того, что init может изменять значение self .

Создайте новый ‘view’ xib с помощью Xcode. Установите для класса владельца файла значение NSViewController, а для его view выхода установите ваш целевой вид. Установите класс целевого представления в свой подкласс NSView. Создайте свой вид, подключите розетки и т.д.

Теперь в вашем подклассе NSView реализуйте назначенные инициализаторы:

 - (id)initWithFrame:(NSRect)frameRect
{
    NSViewController *viewController = [[NSViewController alloc] init];
    [[NSBundle mainBundle] loadNibNamed:@"TheNib" owner:viewController topLevelObjects:NULL];

    id viewFromXib = viewController.view;
    viewFromXib.frame = frameRect;
    self = viewFromXib;
    return self;
}
  

И то же самое с initWithCoder: , чтобы это также работало при использовании вашего подкласса NSView в другом xib.

Ответ №5:

 class CustomView: NSView {

@IBOutlet weak var view: NSView!
@IBOutlet weak var textField: NSTextField!

required init(coder: NSCoder) {
    super.init(coder: coder)!

    let frameworkBundle = Bundle(for: classForCoder)
    assert(frameworkBundle.loadNibNamed("CustomView", owner: self, topLevelObjects: nil))
    addSubview(view)
}
  

}