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

Posted

技术标签:

【中文标题】从 nib 以编程方式从 NSView 的子类加载对象【英文标题】:programmatically loading object from subclass of NSView from nib 【发布时间】:2011-05-02 08:46:09 【问题描述】:

如何使用 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 中的顶视图设置为来自 class:MyView 并将 myView 设置为 [NSBundle loadNibNamed:@"MyView" owner:myView]; 的所有者,就会发生这种情况,但似乎并非如此。 我做错了什么?

【问题讨论】:

尝试将它们移到前面,也许它会在后面绘制它并且由于背景而看不到 我刚试过...这没有发生 ;( 在您的 nib 文件中,是否有一个类为 MyView 的***视图? nib 文件中是否还有其他***对象?文件所有者的类别是什么? 在我的笔尖中:文件的所有者类是MyView,其中的顶视图当前是类NSView,但我也尝试过MyView。都没有用 【参考方案1】:

请注意,一个 nib 文件包含:

一个或多个***对象。比如MyView的一个实例; 文件的所有者占位符。这是在加载 nib 文件之前存在的对象。由于在nib文件加载之前就存在,所以不能和nib文件中的view相同。

场景一: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

虽然这可行,但我不会依赖它,因为它没有记录在案。

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

场景二:NSViewController

Cocoa 提供NSViewController 来管理视图;通常,从 nib 文件加载的视图。您应该创建一个 NSViewController 的子类,其中包含所需的任何出口。编辑 nib 文件时,设置 view 插座和您可能定义的任何其他插座。您还应该设置 nib 文件的所有者,以便它的类是 NSViewController 的这个子类。那么:

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

NSViewController在发送-view时会自动加载nib文件并设置对应的outlets。或者,您可以通过发送-loadView 来强制它加载 nib 文件。

【讨论】:

我的笔尖只有一个***对象,其类是“MyView”,但文件所有者也是“MyView”,这可能吗?。 如果在加载 nib 文件之前已经创建了另一个 MyView 实例,这是可能的。请注意,这意味着这个其他实例要么以编程方式创建,要么从另一个 nib 文件加载,与 MyView.nib 不同。 我有 MyView *myView 实例,它是使用 [[MyView alloc] initWithFrame:]; 创建的我在 [NSBundle loadNib:@"MyView" owner:myView];我仍然不明白为什么这不起作用;( @nacho4d 当然可以,但它与从 nib 文件加载的不同。这将是一个不同的观点,这就是你面临问题中描述的问题的原因。正如我在回答中所写的那样,“由于它(文件的所有者)在加载 nib 文件之前就存在,因此它不能与 nib 文件中的视图相同。” 是的,经常使用 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:&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 的 File's Owner 设置为新视图的 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 的值这一事实。

使用 Xcode 创建一个新的“视图”xib。将 File Owner 的类设置为 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 子类时它也可以工作。

【讨论】:

【参考方案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)

【讨论】:

以上是关于从 nib 以编程方式从 NSView 的子类加载对象的主要内容,如果未能解决你的问题,请参考以下文章

当我以编程方式从情节提要加载窗口时,为啥在 NSView 中未调用 drawRect?

从 nib 文件加载的 NSView 已禁用控件并且操作也不起作用

从 Nib 加载 NSView 的多个实例

Cocoa - 从 nib 加载视图并将其显示在 NSView 容器中,作为子视图

如何将 nib 自定义视图绑定到 NSVIew 子类

从 viewcontrollernib 加载 NSView