首页 > 解决方案 > 了解错误“在展开可选值时意外发现 nil”

问题描述

我对编码/Swift 完全陌生,我知道这个问题问了很多。我搜索并阅读,但我不完全理解它。

我将 UIImageView 连接到我的视图控制器:

@IBOutlet var bi01: UIImageView!

我通过 Xcode 界面设置了一个图像,但以防万一我也通过代码设置了相同的图像:

override func viewDidLoad() {
    super.viewDidLoad()
    bi01.image = //image
}

所以据我所知 bi01 在这一点上不是零?

我用一个按钮来改变 bi01 的形象:

@IBAction func bb01(_ sender: UIButton) {
    updatebi01()
}

并为它创建了一个数组:

let oneToTwo = [image1, image2, image3]

因此,如果我在 viewcontroller.swift 中编写,此功能将起作用:

func updatebi01() {
    bi01.animationImages = oneToTwo
    bi01.animationDuration = 0.3
    bi01.animationRepeatCount = 1
    bi01.startAnimating()
}

到目前为止没有问题。但是我想对许多图片使用相同的功能,所以我创建了一个新的 swift 文件并将这个功能移到那里,我还为 viewcontroller 创建了一个实例:

import Foundation

var screenInstance = ViewController()

func updatebi01() {
    screenInstance.bi01.animationImages = oneToTwo
    screenInstance.bi01.animationDuration = 0.3
    screenInstance.bi01.animationRepeatCount = 1
    screenInstance.bi01.startAnimating()
}

在此之后,当我尝试该按钮时,xcode 在这一行中给了我这个著名的错误:

screenInstance.bi01.animationImages = oneToTwo

现在,我可以在 viewcontroller.swift 中使用这个代码,我可以通过忽略我的第二次尝试/方式来处理这个问题。但我不明白问题本身。

我只是想学习/理解这一点,它是相同的代码。它可以在 viewcontroller.swift 内部工作,但不能在它之外工作。

标签: iosswift

解决方案


您面临的问题不是因为您在视图控制器之外执行此操作,而是因为您实例化视图控制器的方式以及您调用updatebi01(). 这不是强制展开的问题,而是视图控制器管道的问题。

首先,如果您有网点,@IBOutlet我希望您使用故事板。要从情节提要中实例化视图控制器,您需要在情节提要中设置一个标识符,然后像这样使用它:

var screenInstance = UIStoryboard(name: "fileNameHereWithoutExtension", bundle: nil).instantiateViewController(withIdentifier: "idYouSetUpInStoryboard") as! ViewController

所以一个非常快速的解释:通过像你一样调用一个默认构造函数,并ViewController()没有真正添加任何字段。的实例ViewController可能具有完全不同的设计,并且可以由多个故事板或标识符实例化。所以你所做的只是创建了一个类。在您的情况下,您可以执行以下操作:

var screenInstance: ViewController = {
    let controller = ViewController()
    controller.bi01 = {
        let view = UIImageView()
        view.frame = CGRect.zero
        controller.view.addSubview(view)
        return view
    }()
    return controller    
}() 

现在这是有道理的。由于您是以编程方式进行的,因此您还需要满足类的所有参数。由于您是强制解包bi01,因此您是在说“实例化此类实例的任何人都需要确保bi01始终设置该实例”。所以设置它。

我希望这可以清除有关以编程方式进行操作的部分。但是现在让我们假设您正在使用情节提要。我们来看下面的代码:

let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController") as! ViewController
viewController.bi01.animationImages = oneToTwo

现在视图控制器已正确实例化,并且情节提要中bi01是否nil存在问题。但是有一个技巧:bi01实际上将在被调用之前实例化,viewDidLoad所以在大多数情况下,它将nil在它被创建之后。您需要等待视图控制器实际加载。

所以不要访问视图控制器之外的视图。你需要的是这样的:

class ViewController: UIViewController {
    @IBOutlet private var bi01: UIImageView! // Note private

    var animationData: (images: [UIImage], duration: TimeInterval, repeatCount: Int)!

    override func viewDidLoad() {
        super.viewDidLoad()

        bi01.animationImages = animationData.images
        bi01.animationDuration = animationData.duration
        bi01.animationRepeatCount = animationData.repeatCount
        bi01.startAnimating()
    }
}

现在你打电话:

let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController") as! ViewController
viewController.animationData = (oneToTwo, 0.3, 1)

并且您的视图控制器将在需要时完成其余的工作。

为了解释最后一部分,viewDidLoad将在 setter 之后调用animationData. 实际上,在窗口层次结构中添加控制器时会调用 view did load。所以最常见的做法是:

let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController") as! ViewController
viewController.animationData = (oneToTwo, 0.3, 1)
present(viewController, animated: true, completion: nil)

您还可以争辩说您可以通过执行以下操作来访问视图:

let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController") as! ViewController
present(viewController, animated: true) {
    viewController.bi01... // This will work
}

但问题是在呈现视图之后调用该块,也就是在动画之后。这意味着用户将首先看到默认值,当动画结束时,视图会突然改变以反映您正在设置的数据。另一方面,View did load 将在动画开始之前被调用。

还有更多内容,但我希望这可以为您解决一些问题。


推荐阅读