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

时间:2011-05-02 08:46:09

标签: objective-c cocoa macos

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

我希望它不是从头开始动态加载所以我制作了一个MyView.Xib 从MyDocument.m我做了:

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

并且背景很好(它调用drawrect:它按预期绘制) 但是我放在xib上的所有按钮都不会出现。 我已经检查了,并且它们已被加载但是它们的超级视图与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,它们在IB中正确连接,这就是为什么它们加载得很好(我可以参考它们)。但是顶视图没有IBOutlet。因此,当NSBundle加载nib时,顶视图会被分配给其他对象。如果我将IB中的顶视图设置为来自班级MyView并将myView作为所有者[NSBundle loadNibNamed:@"MyView" owner:myView];,我认为会发生这种情况,但似乎并非如此。 我做错了什么?

5 个答案:

答案 0 :(得分:30)

请注意,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会自动加载nib文件,并在发送时-view设置相应的插座。或者,您可以通过发送-loadView强制它加载nib文件。

答案 1 :(得分:5)

您可以参考此类别

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

答案 2 :(得分:1)

我发现@Bavarious的答案非常有用,但我仍然需要一些令人讨厌的东西让它适用于我的用例 - 将几个xib的视图定义中的一个加载到现有的"容器中#34;视图。这是我的工作流程:

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

答案 3 :(得分:0)

<!--assign the property to operation scope-->
    <property name="ITERATOR_DATA_PAYLOAD"
               expression="get-property('DATA_PAYLOAD')"
               scope="operation"
               type="OM"/>

<iterate xmlns:ns="http://org.apache.synapse/xsd"
              continueParent="true"
              expression="//bookstore/book"
              sequential="true">
        <target>
           <sequence>
          <!--if want to assign the property-->
              <property name="DATA_PAYLOAD"
                        expression="get-property('operation','ITERATOR_DATA_PAYLOAD')"
                        type="OM"/>
         </sequence>
        </target>
</iterate>
     <!--Outside the iterator-->
     <property xmlns:ns="http://org.apache.synapse/xsd"
               name="NEW_DATA_PAYLOAD"
               expression="get-property('operation','ITERATOR_DATA_PAYLOAD')"
               type="OM"/>

}

答案 4 :(得分:0)

这是一种编写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子类时也能正常工作。