图片无法在 table 视图中显示
images can't be display in table view
我创建了一个 table带有自定义单元格的视图,每个单元格都有一个图像。
在模型 class 中,我创建了一个 func mainPulatesData()
以使用 URLSession
dataTask 方法从 url 中检索数据,并将数据转换为 UIImage
在完成处理程序块中,然后将图像添加到 UIImage
数组的变量中。
检索数据并将它们添加到 UIImage
数组的过程在 DispatchQueue.global(qos: .userInitiated).async
块中执行。根据打印消息,图像确实已添加到数组中。
但是,即使我在 tableView controller 中创建了模型 class 的实例,并在 viewDidlLoad
中调用了 mainPulatesData()
,图像也没有显示在 table.
根据 table 视图控制器 class 中的其他打印消息,我发现它甚至可以添加到模型 class 中的数组中,但它似乎不起作用table视图控制器中模型 class 的实例。
这是模型 class 中获取图像数据的代码:
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
}
您应该注意此处函数调用的顺序:
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.
}
图片不显示是因为你在后台线程(异步)下载它们,并且loadImages()
被同步调用。这意味着 loadImage()
在 myModel.mainPulatesData()
执行之前被调用,所以当您的图像被下载时,tableview
没有被更新(reloadData()
没有被调用)。您应该创建 Protocol
以通知 UIViewController
,数据已下载,或使用 Completion Handler
.
我正在使用的处理程序的简单示例,我在我的 viewDidLoad
中调用它,它请求来自服务器的数据和 return [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)
原因是图像或 imageList
在您 reloadData()
之后调用 cellForRowAt
时尚未准备好。
一个好的做法是在开始时使用占位符图像,并且仅在 table 视图单元格可见时才加载图像,而不是一次加载所有内容。类似于:
// 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()
}
}
我创建了一个 table带有自定义单元格的视图,每个单元格都有一个图像。
在模型 class 中,我创建了一个 func mainPulatesData()
以使用 URLSession
dataTask 方法从 url 中检索数据,并将数据转换为 UIImage
在完成处理程序块中,然后将图像添加到 UIImage
数组的变量中。
检索数据并将它们添加到 UIImage
数组的过程在 DispatchQueue.global(qos: .userInitiated).async
块中执行。根据打印消息,图像确实已添加到数组中。
但是,即使我在 tableView controller 中创建了模型 class 的实例,并在 viewDidlLoad
中调用了 mainPulatesData()
,图像也没有显示在 table.
根据 table 视图控制器 class 中的其他打印消息,我发现它甚至可以添加到模型 class 中的数组中,但它似乎不起作用table视图控制器中模型 class 的实例。
这是模型 class 中获取图像数据的代码:
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
}
您应该注意此处函数调用的顺序:
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.
}
图片不显示是因为你在后台线程(异步)下载它们,并且loadImages()
被同步调用。这意味着 loadImage()
在 myModel.mainPulatesData()
执行之前被调用,所以当您的图像被下载时,tableview
没有被更新(reloadData()
没有被调用)。您应该创建 Protocol
以通知 UIViewController
,数据已下载,或使用 Completion Handler
.
我正在使用的处理程序的简单示例,我在我的 viewDidLoad
中调用它,它请求来自服务器的数据和 return [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)
原因是图像或 imageList
在您 reloadData()
之后调用 cellForRowAt
时尚未准备好。
一个好的做法是在开始时使用占位符图像,并且仅在 table 视图单元格可见时才加载图像,而不是一次加载所有内容。类似于:
// 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()
}
}