2017-05-11 5 views
1

J'ai créé une tableView avec des cellules personnalisées pour lesquelles chaque cellule a une image.Les images ne peuvent pas être affichées dans la vue de tableau

Dans la classe modèle, j'ai créé un func mainPulatesData() utiliser URLSession méthode dataTask pour récupérer des données à partir de l'URL, et convertir les données en UIImage dans le bloc du gestionnaire d'achèvement, puis ajouter une image dans une variable de tableau de UIImage.

Le processus de récupération des données et leur ajout dans le tableau UIImage a été exécuté dans le bloc DispatchQueue.global(qos: .userInitiated).async. en fonction du message d'impression, les images ont été ajoutées dans le tableau.

Cependant, même si j'ai créé une instance de classe de modèle dans le contrôleur tableView et invoque le mainPulatesData() dans viewDidlLoad, l'image n'apparaissait pas dans la table. Basé sur un autre message d'impression dans la classe de contrôleur de vue de table, j'ai même trouvé qu'il peut être ajouté dans un tableau dans la classe de modèle, mais il semble ne pas fonctionner sur l'instance de la classe de modèle dans le contrôleur tableView.

qui est le code dans la classe de modèle pour obtenir des données d'image:

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() 
     } 
    } 
} 

qui est le code dans le contrôleur de vue de créer une instance de modèle:

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 
} 
+0

c'est le concept de chargement paresseux. Pour cela, vous pouvez utiliser SDWebImage (bibliothèque tierce) –

Répondre

0

La raison en est que les images ou imageList n'est pas prêt au moment cellForRowAt est appelé après vous reloadData().

Une bonne pratique consiste à utiliser des images d'espace réservé au début et à ne charger l'image que lorsqu'une cellule de vue de table est visible au lieu de tout à la fois. Quelque chose comme:

// 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() 
    } 
} 
+0

merci pour votre suggestion, mais il est demandé d'utiliser la classe de modèle à gérer les données, le contrôleur ne peut rien savoir sur les données. dans ce cas, que dois-je faire? – CEz

+0

@CEz J'ai mis à jour ma réponse. J'espère que c'est ce que tu veux. – xiangxin

+0

merci! aide beaucoup! – CEz

0

Vous devez prendre note de la séquence de votre appel de fonction ici:

myModel.mainPulatesURLs() --> populates myURLs 
myModel.mainPulatesData() --> populates image from myURLs in forloop asynchronously. 
loadImages() --> called asynchronously. 

pendant que vous chargez vos images de myModel.mainPulatesData() vous avez déjà appelé loadImages() dont myModel.imageList était encore vide.

vous devriez appeler loadImages() après un rappel de myModel.mainPulatesData() ou lorsque vous êtes sûr que les images étaient déjà chargées.

vous pouvez utiliser dispatch_group_t pour configurer les rappels.

ici comme demandé:

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. 
} 
+0

pourriez-vous m'en dire plus sur 'dispatch_group_t'? – CEz

+0

@CEz J'ai ajouté un échantillon avec des commentaires comme demandé. aussi voici votre référence: https://developer.apple.com/reference/dispatch/dispatch_group_t – nferocious76

0

Les images ne sont pas affichées parce que vous les télécharger en fil de fond (de façon asynchrone) et loadImages() est appelé de manière synchrone. Cela signifie que loadImage() est appelé avant que myModel.mainPulatesData() ne soit exécuté, donc lorsque vos images sont téléchargées, tableview n'est pas mis à jour (reloadData() n'est pas appelé). Vous devez créer Protocol pour notifier UIViewController, que les données ont été téléchargées ou utiliser Completion Handler.

Exemple simple de gestionnaire, je me sers, je l'appelle dans mon viewDidLoad, il demande des données du serveur et retourne un tableau de [Reservation]?

ReservationTableModule.requestAllReservations { [weak self] reservations in 
      guard let `self` = self else { 
       return 
      } 

      guard let `reservations` = `reservations` else { 
       return 
      } 
      self.reservations = reservations 
      .reservationsTableView.reloadData() 
     } 

c'est fonction de la demande réelle

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 est appelé gestionnaire d'achèvement, vous devriez, je suppose que le faire handler: @escaping ([UIImage]?) -> Void et après vos données téléchargées appel handler(reservations)

+0

pourriez-vous donner un exemple de code? Je ne suis pas familier avec le protocole et le gestionnaire d'achèvement. BTW, il est demandé de gérer les données dans la classe de modèle, le contrôleur ne devrait rien savoir sur les données. – CEz

+0

@CEz a ajouté un exemple – JuicyFruit

+0

alors voulez-vous dire que j'ai besoin de créer une classe appelée 'ReservationTableModule' avec la méthode' func funcAllReservations'? alors appelez-le dans 'viewDidLoad()'? – CEz