图像无法在表格视图中显示
Posted
技术标签:
【中文标题】图像无法在表格视图中显示【英文标题】:images can't be display in table view 【发布时间】:2017-05-11 06:26:12 【问题描述】:我创建了一个带有自定义单元格的 tableView,每个单元格都有一个图像。
在模型类中,我创建了一个func mainPulatesData()
,使用URLSession
dataTask方法从url中检索数据,并在完成处理程序块中将数据转换为UIImage
,然后将图像添加到@数组的变量中987654324@。
检索数据并将它们添加到UIImage
数组的过程在DispatchQueue.global(qos: .userInitiated).async
块中执行。根据打印消息,图像确实被添加到数组中。
然而,即使我在tableView控制器中创建了一个模型类的实例,并在viewDidlLoad
中调用mainPulatesData()
,图像也没有出现在表格中。
根据表格视图控制器类中的其他打印消息,我发现它甚至可以添加到模型类中的数组中,但似乎不适用于 tableView 控制器中的模型类实例。
这是模型类中获取图像数据的代码:
func mainPulatesData()
let session = URLSession.shared
if myURLs.count > 0
print("\(myURLs.count) urls")
for url in myURLs
let task = session.dataTask(with: url, completionHandler: (data,response, error) in
let imageData = data
DispatchQueue.global(qos: .userInitiated).async
if imageData != nil
if let image = UIImage(data: imageData!)
self.imageList.append(image)
print("\(self.imageList.count) images added.")
else
print("nil")
)
task.resume()
这是视图控制器中创建模型实例的代码:
override func viewDidLoad()
super.viewDidLoad()
myModel.mainPulatesURLs()
myModel.mainPulatesData()
loadImages()
private func loadImages()
if myModel.imageList.count > 0
tableView.reloadData()
else
print("data nil")
override func numberOfSections(in tableView: UITableView) -> Int
return 1
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return myModel.imageList.count
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as! ImageTableViewCell
if myModel.imageList.count > 0
let image = myModel.imageList[indexPath.row]
cell.tableImage = image
return cell
return cell
【问题讨论】:
是延迟加载的概念。为此,您可以使用 SDWebImage(第三方库) 【参考方案1】:原因是图像或imageList
在你reloadData()
之后调用cellForRowAt
时还没有准备好。
一个好的做法是在开始时使用占位符图像,并且仅在表格视图单元格可见时才加载图像,而不是一次加载所有内容。比如:
// VC class
private var modelList = [MyModel(url: url1), MyModel(url: url2), ...]
override func numberOfSections(in tableView: UITableView) -> Int
return 1
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return modelList.count
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as! ImageTableViewCell
cell.update(model: modelList[indexPath.row])
return cell
// Cell class
@IBOutlet weak var cellImageView: UIImageView!
func update(model: MyModel)
model.fetchImage(callback: image in
self.cellImageView.image = image
)
// Model class
final class MyModel: NSObject
let url: URL
private var _imageCache: UIImage?
init(url: URL)
self.url = url
func fetchImage(callback: @escaping (UIImage?) -> Void)
if let img = self._imageCache
callback(img)
return
let task = URLSession.shared.dataTask(with: self.url, completionHandler: data, _, _ in
if let imageData = data, let img = UIImage(data: imageData)
self._imageCache = img
callback(img)
else
callback(nil)
)
task.resume()
【讨论】:
感谢您的建议,但要求使用模型类处理数据,控制器对数据一无所知。在这种情况下,我该怎么办? @CEz 我已经更新了我的答案。希望这就是你想要的。【参考方案2】:图像不显示,因为您在后台线程(异步)下载它们,并且同步调用loadImages()
。这意味着在执行myModel.mainPulatesData()
之前调用loadImage()
,因此当您下载图像时,tableview
不会被更新(reloadData()
不会被调用)。您应该创建Protocol
以通知UIViewController
,数据已下载,或使用Completion Handler
。
我正在使用的处理程序的简单示例,我在我的viewDidLoad
中调用它,它从服务器请求数据并返回一个[Reservation]?
数组
ReservationTableModule.requestAllReservations [weak self] reservations in
guard let `self` = self else
return
guard let `reservations` = `reservations` else
return
self.reservations = reservations
.reservationsTableView.reloadData()
这是实际的请求函数
class func requestAllReservations(handler: @escaping ([Reservation]?) -> Void)
let url = "reservations/all"
APIModel.shared.requestWithLocation(.post, URL: url, parameters: nil) data in
let reservations = data?["reservations"].to(type: Reservation.self) as? [Reservation]
handler(reservations)
handler: @escaping ([Reservation]?) -> Void
被称为完成处理程序,你应该,我猜它是 handler: @escaping ([UIImage]?) -> Void
并在你的数据下载后调用 handler(reservations)
【讨论】:
你能给出一些示例代码吗?我不熟悉协议和完成处理程序。顺便说一句,它被要求处理模型类中的数据,控制器不应该知道任何关于数据的事情。 @CEz 添加了一个例子 你的意思是我需要用class func requestAllReservations
方法创建一个名为ReservationTableModule
的类吗?然后打电话给viewDidLoad()
?
@CEz 不,这是一个如何使用回调函数的示例【参考方案3】:
您应该注意这里的函数调用顺序:
myModel.mainPulatesURLs() --> populates myURLs
myModel.mainPulatesData() --> populates image from myURLs in forloop asynchronously.
loadImages() --> called asynchronously.
当您从myModel.mainPulatesData()
加载图像时,您已经调用了loadImages()
,而myModel.imageList
仍然是空的。
您应该在来自myModel.mainPulatesData()
的回调之后调用loadImages()
,或者当您确定图像已经加载时。
您可以使用dispatch_group_t
来配置回调。
按要求在此处:
import UIKit
var myURLs: [String] = ["urlA", "urlB", "urlC", "urlD"]
// we define the group for our asynchronous fetch. also works in synchronous config
var fetchGroup = DispatchGroup()
for urlString in myURLs
// for every url fetch we define, we will call an 'enter' to issue that there is a new block for us to wait or monitor
fetchGroup.enter()
// the fetch goes here
let url = URL(string: urlString)!
URLSession.shared.downloadTask(with: URLRequest(url: url), completionHandler: (urlReq, urlRes, error) in
// do your download config here...
// now that the block has downloaded the image, we are to notify that it is done by calling 'leave'
fetchGroup.leave()
).resume()
// now this is where our config will be used.
fetchGroup.notify(queue: DispatchQueue.main)
// reload your table here as all of the image were fetched regardless of download error.
【讨论】:
你能告诉我更多关于dispatch_group_t
的信息吗?
@CEz 我根据要求添加了一个带有 cmets 的示例。这也是您的参考:developer.apple.com/reference/dispatch/dispatch_group_t以上是关于图像无法在表格视图中显示的主要内容,如果未能解决你的问题,请参考以下文章
表格视图单元格中的滚动视图将无法正确显示图像,并且在 swift 4 中的分页模式下有一个额外的页面