#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)
}
}