从nib文件中以编程方式加载NSView子类的对象

19

如何使用Xib正确加载NSView的子类对象?

我希望它能够动态地加载,而不是从一开始就加载,所以我创建了一个名为MyView.Xib的文件。
在MyDocument.m中,我执行了以下操作:

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

提前致谢。

编辑

我想我已经意识到问题的根源了...(??)

在我的 MyView 类中,我有各种正确连接的 IBOutlet,这就是为什么它们能够很好地加载(我可以引用它们)。然而,对于顶部视图没有 IBOutlet。因此,当 NSBundle 加载 nib 时,顶部视图会被分配给其他对象。我认为如果我在 IB 中将我的顶部视图设置为 MyView 类,并将 myView 设置为 owner,如下所示:[NSBundle loadNibNamed:@"MyView" owner:myView];但似乎情况并非如此。我做错了什么?


尝试将它们移动到前面,也许它是被绘制在后面,因为背景而看不见。 - Radu
我刚刚尝试了...但那并没有发生 ;( - nacho4d
在你的nib文件中,是否有一个顶层视图其类是MyView?nib文件中是否还有其他顶层对象?文件所有者的类是什么? - user557219
在我的nib文件中,File's Owner的类是MyView,它的顶层视图当前的类是NSView,但我也尝试过MyView,但都没有成功。 - nacho4d
5个回答

35

请注意,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:&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:&topLevelObjects]) // error

虽然这个方法可以工作,但是我不会依赖它,因为它没有文档记录。

请注意,您拥有顶层对象的所有权,因此在不再需要它们时,您负责释放它们。

场景2:NSViewController

Cocoa提供了NSViewController来管理视图;通常,从nib文件加载的视图。您应该创建一个包含所需输出的NSViewController子类。在编辑nib文件时,设置view输出和任何其他定义的输出。您还应该设置nib文件的所有者,以使其类是NSViewController的这个子类。然后:

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

NSViewController 在你发送 -view 时,会自动加载 nib 文件并设置相应的 outlet。另外,你也可以通过发送 -loadView 强制加载 nib 文件。


我的 nib 文件只有一个顶级对象,它的类是 "MyView",但文件所有者也是 "MyView",这种情况可能吗? - nacho4d
我有一个名为MyView *myView的实例,该实例是使用[[MyView alloc] initWithFrame:]创建的;我正在使用[NSBundle loadNib:@"MyView" owner:myView];将其加载。我仍然不明白为什么这不起作用 ;( - nacho4d
@nacho4d 当然可以,但它不会与从 Nib 文件加载的视图相同。它将是一个不同的视图,这就是为什么您面临问题的原因。正如我在答案中所写的那样,“由于文件所有者在Nib文件加载之前存在,因此它不能与Nib文件中的视图相同。” - user557219
如果你从iOS的角度来看,通常nib文件的所有者是UIViewController的某个子类,而不是UIView的子类。 - user557219
1
是的,UIViewController 的子类通常被使用,但有时我会将其作为 UIView 的子类,因为我可以在需要时加载特定的 UIView,而不是在视图控制器加载时立即加载它。(因为通常这是一些不总是可见的东西,或者是从一开始就很昂贵的东西)我想在我正在尝试做的这个 Mac 应用程序中实现同样的功能。 - nacho4d
显示剩余4条评论

6

您可以参考此类别

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:&objects]) {
        NSLog(@"Couldn't load nib named %@", nibNamed);
        return nil;
    }

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

1
这里有一种编写 NSView 子类的方法,使得视图本身完全来自单独的 xib 并且一切都设置正确。它依赖于 init 可以更改 self 值的事实。
使用 Xcode 创建一个新的 'view' xib。将文件所有者的类设置为 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:,这样在另一个 xib 中使用您的 NSView 子类时也可以正常工作。


1

我发现来自@Bavarious的答案非常有用,但是为了适应我的使用情况,我仍然需要自己进行一些调整——将多个xib视图定义之一加载到现有的“容器”视图中。以下是我的工作流程:

1)在.xib文件中,按预期在IB中创建视图。

2)不要为新视图的viewController添加对象,而是

3)将.xib文件的File's Owner设置为新视图的viewController

4)将任何outlets和actions连接到File's Owner,并具体指定.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
    }
}

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

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接