如何加载一个带有顶层ViewController的XIB文件?

7
我有一个带有视图控制器根元素的 nib,像这样:

enter image description here

因此,我可以使用自动布局相对于顶部和底部布局指南定位元素。

当我第一次尝试使用以下方式加载此 nib 时:

SearchViewControllerPro* searchViewController = [[SearchViewControllerPro alloc]initWithNibName:@"SearchViewControllerPro" bundle:[NSBundle mainBundle]];

我遇到了以下运行时异常:
终止应用程序,因为未捕获的异常 'NSInternalInconsistencyException',原因是:'-[UIViewController _loadViewFromNibNamed:bundle:] 加载了“SearchViewControllerPro”nib,但视图输出口未设置。'
通过搜索错误信息,我发现需要将xib文件的文件所有者设置为我的视图控制器类,并将视图输出口设置为xib中的视图对象。如果这样做,那么我会得到以下运行时错误:
终止应用程序,因为未捕获的异常 'UIViewControllerHierarchyInconsistency',原因是:'一个视图只能与一个视图控制器关联! 视图>与关联。在将此视图与之关联之前,请清除此关联。'
这并不令人惊讶,因为该视图同时与xib的文件所有者和顶级视图控制器相关联。但是,我如何告诉运行时它们实际上是同一件事情,而不是两个单独的实体?
编辑: 当我尝试从nib中解压缩ViewController时,如下所示:
NSArray* xibContents = [[NSBundle mainBundle] loadNibNamed:@"SearchViewControllerPro" owner:nil options:nil];
SearchViewControllerPro* mapSearchViewController = [xibContents lastObject];

它也毫无用处:

由于未捕获的异常“NSUnknownKeyException”,应用程序终止, 原因:'[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key view。

临时解决方案:

我找到了一个解决方法,但它不太好看。尽管在IB中显示的结构如此,但视图控制器不是xib中的最后一个对象。 所以我有:

__block SearchViewControllerPro* mapSearchViewController = nil;
[xibContents enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if ([obj isKindOfClass:[SearchViewControllerPro class]]) {
        mapSearchViewController = obj;
    }
}];

这样做似乎可以避免运行时崩溃。但是,这并不是干净的代码。


关于您的编辑...您的nib中存在错误。您需要确保类名正确,并手动断开任何不正确的连接。您将FilesOwner设置为什么类?您与FilesOwner建立了哪些连接? - hooleyhoop
没关系,无论你将FilesOwner的类设置为什么,只要你意识到它会根据你是使用[NSBundle mainBundle] loadNibNamed:...还是SearchViewControllerPro alloc]initWithNibName:...而改变,因此你所建立的任何与FilesOwner的连接都将无效(你会崩溃),如果你更改了加载方法。 - hooleyhoop
3个回答

4
您如何告诉运行时,它们实际上是同一件事而不是两个独立实体?
您无法这样做,因为它们不是同一件事。您创建了两个SearchViewControllerPro实例。
您需要使用alloc-init来创建SearchViewControllerPro实例,或者从nib中解档一个实例。
如果您决定在nib中创建ViewController,则通常访问它的方式与访问其他项目(视图、按钮、文本字段)的方式相同。
将outlet添加到FilesOwner对象中,在接口构建器中连接连接,并确保在解档nib时传递对象。
例如,如果要将解档nib的对象设置为FilesOwner:
[[NSBundle mainBundle] loadNibNamed:@"SearchViewControllerPro" owner:self options:nil];

谢谢您的回答。虽然我不确定我是否理解了它。我刚试图从nib中获取“ViewController”,但没有成功(请参见我的编辑,这是您的意思吗?)。关于您的评论:是的,我知道现在我有两个实例。我的意思是,我想将它们解析为同一个对象,但不知道如何在IB或代码中实现,因为分配初始化失败(请参见上面的错误)。 - Chris
“将两个实例解析为同一个对象”是什么意思?如果我这样做:ob1 = [SearchViewControllerPro alloc] init]; ob2 = [SearchViewControllerPro alloc] init];(这就是你所做的),那么如何将它们解析为一个实例?两个对象就是两个对象。 - hooleyhoop
2
让我重新表述问题:我知道如何将nib作为视图加载到“UIViewController”中。由于iOS7需要相对于topLayoutGuide重新定位元素,因此我不得不将视图控制器作为nib中的顶级元素,并且迄今为止我的尝试都失败了。您正确地描述了症状-由于nib的结构和我尝试实例化视图控制器的方式,我得到了两个对象。但是我不知道如何设置nib和加载调用,以便最终只得到一个对象。希望这样解释更清楚了。 - Chris
感谢您更新答案。为整个视图控制器创建一个插座是一个很棒的技巧。 :-) - Chris
关于 [[NSBundle mainBundle] loadNibNamed: 的一个注意事项 - 它会在内部调用 viewDidLoad 方法。因此,如果我们从代码中设置视图控制器的某些属性以在 viewDidLoad 方法中使用它们,就会遇到问题... - brigadir

3
以下代码同样有效,而且(至少对我来说)比为ViewController创建出口更明确:
NSArray* xibContents = [[NSBundle mainBundle] loadNibNamed:@"SearchViewControllerPro" owner:nil options:nil];
SearchViewControllerPro* mapSearchViewController = [xibContents objectAtIndex:0];

0
if (isIPad)
{
    [[NSBundle mainBundle] loadNibNamed:@"ABC_iPad" owner:self
options:nil];

}
else{
    [[NSBundle mainBundle] loadNibNamed:@"ABC_iPhone" owner:self options:nil];

}

尽管这段代码可能有助于解决问题,但它并没有解释为什么以及如何回答这个问题。提供这种额外的上下文将显著提高其长期教育价值。请编辑您的答案以添加说明,包括适用的限制和假设。 - Toby Speight

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