iOS 8 beta 5中使用nib文件的视图控制器是否存在问题?

12

我在 iOS 8 beta 4 中创建了一个测试项目,其中包括一个主视图控制器和一个作为 UIViewController 子类及带有 xib 文件的第二个视图控制器。

我在主控制器上放置了一个按钮来呈现第二个控制器:

class ViewController: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
}

@IBAction func testVCBtnTapped() {
    let vc = TestVC()
    presentViewController(vc, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

我运行这个应用程序,按下按钮后呈现第二个控制器-一切都很好。

升级到xcode beta 5后,当我按下按钮时屏幕会变黑。

因为我知道他们改动了初始化代码,���以我试着添加重载来看是否可以修复它:

class TestVC: UIViewController {

override init() {
    super.init()
}
required init(coder aDecoder: NSCoder!) {
    super.init(coder: aDecoder)
}
override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
}

同样的问题。在xcode接受的所有可能组合中更改“required”和“overrides”没有任何效果。

如果我使用storyboard创建另一个控制器并进行segue,一切都好。

有什么想法吗?

编辑-新信息

  1. 在init中尝试nibName = nil-同样的问题
  2. 用objective c创建相同的应用程序,它可以正常工作

显然是Swift beta 5的问题


找到解决方法了!好吧,其实不是我自己找到的,是苹果公司的某个人告诉我的。:( - matt
新的解决方法:在.xib文件名前加上模块名称。 - matt
7个回答

25

我不能确定这是一个错误还是改变,但它肯定有所不同。当然他们可能会把它改回来... 无论如何,在种子5中的规则是:

视图控制器不会自动找到与其同名的.xib文件,您必须明确提供名称。

因此,在您的情况下,任何尝试初始化此视图控制器都必须最终使用显式的nib名称("TestVC")调用nibName:bundle:,我想。

如果您希望能够通过调用

let vc = TestVC()

如果您在呈现视图控制器中所做的一样,那么只需在呈现的视图控制器中重写init(),调用super.init(nibName:"TestVC", bundle:nil),这就是您所需要的(除非我认为您还需要讨论这里init(coder:)防止器)。

编辑 您完全正确,这是一个仅适用于Swift的问题。很好地发现了。在Objective-C中,使用init(或new)进行初始化仍然可以正常工作;视图控制器可以正确找到它同名的.xib文件。

另一个编辑 决定因素不是Objective-C或Swift调用init的方式,而是视图控制器本身是用Objective-C还是Swift编写的。

最终编辑 解决方法是像这样声明您的Swift视图控制器:

@objc(ViewController) ViewController : UIViewController { // ...

括号中的名称消除了导致问题的命名短语。很可能在下一个发布版本中,这个问题会被修复,然后您可以再次删除@objc

另一个最终编辑坏消息是:我提交的错误报告回来说“按预期工作”。他们指出,我所要做的就是将.xib文件命名为包含模块的名称,例如,如果我的应用程序叫做NibFinder,那么如果我将.xib文件命名为NibFinder.ViewController.xib,在实例化ViewController()时,它会自动找到。

这是正确的,但在我看来,它只是重申了这个错误;Swift查找过程正在前置模块名称。因此,苹果公司认为我应该屈服并在.xib文件中前置相同的模块名称,而我认为苹果公司应该屈服并在执行搜索时剥离模块名称。

真正最终的编辑:这个bug已经在iOS 9 beta 4中得到了修复,所有这些解决方法都变得不必要。


无论如何,请提交一个错误报告。你有合理的抱怨:他们破坏了你的代码。另一方面,我有一种不好的感觉,这可能是故意的,正因为,就像你说的那样,他们现在明显在调整初始化程序。 - matt
1
哦,我忘了提到:一个非常奇怪的事情是,如果视图控制器被称为“MyViewController”,而.xib被称为“MyView.xib”(省略了“Controller”部分),使用简单的init()初始化仍然像以前一样工作。换句话说,在一种形式上他们杀死了这个特性,但在另一种形式上它仍然有效。这是因为主要变化是一个错误还是错误是他们忘记撤回这个特性呢?只有时间才能告诉我们... - matt
我有一些只使用xib文件的应用程序,它们运行良好,但只有在我切换到显式nib名称后才能正常工作。将您的项目发布在GitHub上,我会为您修复它。 - matt
Matt - 我试了你的测试应用,但它对我不起作用。你是在使用 beta 5 吗?也许我的 Xcode 副本有问题。我会下载一个新副本并再次尝试。 - Jim T
我展示了失败和解决方案;解决方案在坏代码行后的注释中。因此,只需将坏代码行注释掉并注释好代码行即可。 - matt
显示剩余3条评论

4

如果您需要支持iOS8,您可以在UIViewController上使用扩展。

extension UIViewController {
    static func instanceWithDefaultNib() -> Self {
        let className = NSStringFromClass(self as! AnyClass).componentsSeparatedByString(".").last
        let bundle = NSBundle(forClass: self as! AnyClass)
        return self.init(nibName: className, bundle: bundle)
    }
}

然后,只需按照以下方式创建实例即可。
let vc = TestVC.instanceWithDefaultNib()

3
这个技巧对我很有效。如果您有一个基本的视图控制器类,您可以重写nibName属性,并返回类名(等于xib文件名)。
class BaseViewController: UIViewController {

   override var nibName: String? {
      get {
         guard #available(iOS 9, *) else {
            let nib = String(self.classForCoder)
            return nib
         }

         return super.nibName
       }
    }
}

所有继承于这个基础类的视图控制器都可以加载它们自己的xib文件。 例如,MyViewController: BaseViewController 就可以加载 MyViewController.xib


巧妙。你甚至可以实现截取“Controller”后缀的部分。 - matt
如果您还有未关联 xib 的 ViewControllers,应用程序将在 iOS8 上崩溃。为了解决这个问题,您需要检查 nib 文件是否存在。 - KaterinaPetrova

2

我最近遇到了这个问题,我的解决方案是在baseviewController中覆盖nibName方法。我的解决方案如下:

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

init() {
    super.init(nibName: nil, bundle: Bundle.main)
}

override var nibName: String? {
    get {
        if #available(iOS 9, *) {
            return super.nibName
        } else {
            let classString = String(describing: type(of: self))
            guard Bundle.main.path(forResource: classString, ofType: "nib") != nil else {
                return nil
            }
            return classString
        }
    }
}

0

我在Xcode 10.1和iOS 12上遇到了非常类似的问题。

这段代码有效:

class ExampleViewController: UIViewController {

    init() {
        super.init(nibName: "ExampleViewController", bundle: nil)
    }

}

但是如果将nibName传递为nil,则无法加载XIB。通过使用@objc("ExampleViewController")显式指定类名可以解决此问题。

问题是由模块名称以字母'UI'开头引起的(项目称为UIKitExample)。当我将目标重命名为其他名称时,问题得到解决。


0

Swift3:

extension UIViewController {
    static func instanceWithDefaultNib() -> Self {
        let className = NSStringFromClass(self).components(separatedBy: ".").last
        return self.init(nibName: className, bundle: nil)
    }
}

请在您的答案中添加解释。 - ADreNaLiNe-DJ

0

这是基于Francesco的答案编写的代码。它包含了检查nib文件是否存在的功能,因此当您加载UIViewController时,应用程序不会崩溃,即使它没有关联的xib(您可以在iOS8上重现此问题)。

override var nibName: String? {
    get {
        let classString = String(describing: type(of: self))
        guard nibBundle?.path(forResource: classString, ofType: "nib") != nil else {
            return nil
        }
        return classString
    }
}
override var nibBundle: Bundle? {
    get {
        return Bundle.main
    }
}

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