ios - 了解错误“在展开可选值时意外发现 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 内部工作,但不能在它之外工作。
解决方案
您面临的问题不是因为您在视图控制器之外执行此操作,而是因为您实例化视图控制器的方式以及您调用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 将在动画开始之前被调用。
还有更多内容,但我希望这可以为您解决一些问题。
推荐阅读
- firebase - 灵活引用其他记录的 Firebase 记录
- javascript - Discord.js 类型错误
- java - 为什么我第一次打开eclipse juno时总是出错?
- javascript - 具有多个文件选择的浏览器“粘贴”事件
- php - 如何根据当前经过身份验证的用户动态设置 Laravel 配置?
- google-cloud-build - 使用 JFrog Artifactory 构建云
- sql - 一个 BIGQUERY 中的 2 个 SELECT 语句
- java - 为什么我们用于 pdfview 的依赖项会出现以下错误
- xslt - 第一个前面的兄弟姐妹
- android - Android Compose - 将传统视图与 ComposeView 结合使用