我有一个需要解析和显示的图像URL。URL 存在,但返回 nil。
它通过调用cell.recipeImage.downloadImage(来自:(self.tableViewDataSource[indexPath.item].image))成功地解析了cellForRowAt
函数,图像显示在此行。但是,在didSelectRowAt
中调用它时它不存在
RecipeTableViewController.swift
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let resultsVC = Storyboard.instantiateViewController(withIdentifier: "ResultsViewController") as! ResultsViewController
// Information to be passed to ResultsViewController
if (tableViewDataSource[indexPath.item] as? Recipe) != nil {
if isSearching {
resultsVC.getTitle = filteredData[indexPath.row].title
//resultsVC.imageDisplay.downloadImage(from: (self.filteredData[indexPath.row].image))
} else {
resultsVC.getTitle = tableViewDataSource[indexPath.row].title
// Parse images
resultsVC.imageDisplay.downloadImage(from: (self.tableViewDataSource[indexPath.row].image))
}
}
// Push to next view
self.navigationController?.pushViewController(resultsVC, animated: true)
}
extension UIImageView {
func downloadImage(from url: String) {
let urlRequest = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.sync {
self.image = UIImage(data: data!)
}
}
task.resume()
}
}
结果视图控制器.swift
class ResultsViewController: UIViewController {
var getTitle = String()
var getImage = String()
@IBOutlet weak var recipeDisplay: UILabel!
@IBOutlet weak var imageDisplay: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
recipeDisplay.text! = getTitle
}
...
}
返回错误
线程 1:致命错误:解开可选值时意外发现 nil
据我了解,该应用程序在以下行崩溃:
recipeDisplay.text! = getTitle
如果是,显然这不是正确的方法。只需删除强制展开,因为此处标签上的文本默认为零。强制引用 nil 值将使应用崩溃。
recipeDisplay.text = getTitle
更新: - 让我们确保您正确连接了标签和插座。将 ti 连接到 VC,而不是文件所有者。
您正在对尚未初始化的视图调用与视图相关的代码。请记住,IBOutlet 是隐式解包的属性,因此如果您在初始化之前尝试访问它们,它们将强制解包并崩溃。所以不是UIImage即将出现零,而是配方显示为零并且正在被强制解开。
要做的惯用的iOS事情是将某种视图模型(对象或结构)交给视图控制器,然后在完成加载后让它对该项执行工作。
因此,在 didSelect 方法中,您可以创建视图模型(需要定义)并像这样移交它:
let title = filteredData[indexPath.row].title
let imageURL = self.tableViewDataSource[indexPath.row].image
let viewModel = ViewModel(title: title, imageURL: imageURL)
resultsVC.viewModel = viewModel
然后在你的结果VC中,你会做这样的事情:
override func viewDidLoad() {
super.viewDidLoad()
if let vm = viewModel {
recipeDisplay.text = vm.title
downloadImage(from: vm.imageURL)
}
}
因此,在您的情况下,您需要做的就是将这些字符串交给您的 VC(您可以将它们包装在视图模型中或单独移交它们),然后在该 VC 的viewDidLoad()
中,这就是您调用downloadImage(from:)
的地方。这样,就不会有在加载子视图之前调用子视图的危险。
最后一点:您的下载方法应该更安全一些,因为它使用了data
和error
变量,以及对 self 的引用。请记住,只要您不必使用它,请避免!
使用它(改用可选的链接),除非您有充分的理由这样做,否则请始终在闭包中使用[weak self]
。
我建议这样做:
func downloadImage(from url: String) {
let urlRequest = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: urlRequest) { [weak self] (data,response,error) in
if let error = error {
print(error)
return
}
if let data = data {
DispatchQueue.main.sync {
self?.image = UIImage(data: data)
}
}
}
task.resume()
}
更新:因为"视图模型"的概念一下子有点太多了,让我解释一下。
视图模型只是一个对象或结构,表示屏幕需要处于可显示状态的表示数据。它不是 Apple 定义的类型的名称,也没有在 iOS SDK 中的任何位置定义。这是你需要定义自己的东西。因此,在这种情况下,我建议您在您将要使用它的相同精细中定义它,即在与 ResultsViewController 相同的文件中。
你会做这样的事情:
struct ResultsViewModel {
let title: String
let imageURL: String
}
然后在结果视图控制器上,您将创建一个属性,如下所示:
var viewModel: ResultsViewModel?
或者,如果您不喜欢处理可选选项,则可以执行以下操作:
var viewModel = ResultsViewModel(title: "", imageURL: "")
或者,您可以执行已在执行的操作,但我强烈建议重命名这些属性。getTitle
听起来它除了坚持一个价值之外,还在做更多的事情。title
会是一个更好的名字。同样的批评也适用于getImage
,额外的批评是它也具有误导性,因为它听起来像是在存储图像,但事实并非如此。它存储一个图像网址。imageURL
是一个更好的名字。