2016-06-13 14 views
7

Sono nuovo alla programmazione rapida e iOS, ma sono riuscito a creare un'interfaccia di avvio sostanzialmente stabile per la mia applicazione in Xcode. CollectionView acquisisce un'immagine e un testo da una serie di dizionari creati da un file CSV sul mio server di rete domestico. Il file CVS contiene i dati nel seguente formato (nota, gli URL sono stati cambiati per proteggere le immagini con licenza):swift iOS - UICollectionVisualizzazione delle immagini dopo lo scorrimento veloce

file CSV

csv file is @ url https://myserver.com/csv.txt and contains the following 

Title";"SeriesImageURL 
Title1";"https://licensedimage.com/url1 
Title2";"https://licensedimage.com/url2 
... 
Title1000";"https://licensedimage.com/url1000 

Il problema è che quando si scorre rapidamente attraverso il CollectionView, la cella catturerà l'immagine errata. Notevolmente, se si esegue uno scorrimento lento o medio, verrà visualizzata un'immagine diversa prima che l'immagine corretta venga resa nella cella corretta (il testo dell'etichetta per le celle è sempre corretto, solo l'immagine è sempre disattivata). Dopo che si è verificata la mancata corrispondenza delle immagini con la cella corretta con etichetta, tutte le altre celle di CollectionView mostreranno anche immagini errate.

E.g. La cella 1-9 mostrerà il Titolo 1-9 con la corretta Image1-9 Quando si scorre lentamente, Cell 19-27 mostrerà il Titolo 19-27, mostrerà brevemente l'Immagine 10-18 e mostrerà l'immagine corretta 19-27. Quando si scorre velocemente un numero enorme di celle (ad es. Dalla cella 1-9 alla cella 90-99), le celle 90-99 mostreranno il Titolo 90-99, mostreranno l'Immagine 10-50, e quindi rimarranno erroneamente sull'immagine 41- 50 (o circa). Quando si scorre ulteriormente, Cell 100+ visualizzerà il titolo corretto ma mostrerà solo le immagini dalla gamma Immagine 41-50.

Penso che questo errore sia perché il riutilizzo delle celle non è gestito correttamente, la memorizzazione nella cache delle immagini non è gestita correttamente, o entrambi. Potrebbe anche essere qualcosa che non vedo come programmatore iOS/Swift per principianti. Ho provato a implementare una richiesta con un modificatore di completamento ma non riesco a farlo funzionare correttamente con il modo in cui il mio codice è impostato. Gradirei qualsiasi aiuto con questo, oltre a una spiegazione del motivo per cui la correzione funziona come fa. Grazie!

Il codice pertinente è di seguito.

SeriesCollectionViewController.swift

class SeriesCollectionViewController: UICollectionViewController, UISearchBarDelegate { 

let reuseIdentifier:String = "SeriesCell" 

// Set Data Source Models & Variables 
struct seriesModel { 

    let title: AnyObject 
    let seriesimageurl: AnyObject 
} 
var seriesDict = [String:AnyObject]() 
var seriesArray = [seriesModel]() 

// Image Cache 
var imageCache = NSCache() 

override func viewDidLoad() { 
    super.viewDidLoad() 
    // Grab Data from Source 
    do { 

     let url = NSURL(string: "https://myserver.com/csv.txt") 
     let fullText = try NSString(contentsOfURL: url!, encoding: NSUTF8StringEncoding) 
     let readings = fullText.componentsSeparatedByString("\n") as [String] 
     var seriesDictCount = readings.count 
     seriesDictCount -= 1 
     for i in 1..<seriesDictCount { 
      let seriesData = readings[i].componentsSeparatedByString("\";\"") 
      seriesDict["Title"] = "\(seriesData[0])" 
      seriesDict["SeriesImageURL"] = "\(seriesData[1])" 
      seriesArray.append(seriesModel(
       title: seriesDict["Title"]!, 
       seriesimageurl: seriesDict["SeriesImageURL"]!, 
      )) 
     } 
    } catch let error as NSError { 
     print("Error: \(error)") 
    } 
} 

override func didReceiveMemoryWarning() { 
    super.didReceiveMemoryWarning() 
    imageCache.removeAllObjects() 
    // Dispose of any resources that can be recreated. 
} 

//... 
//...skipping over some stuff that isn't relevant 
//... 

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> SeriesCollectionViewCell { 
    let cell: SeriesCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! SeriesCollectionViewCell 

    if (self.searchBarActive) { 
     let series = seriesArrayForSearchResult[indexPath.row] 
     do { 
      // set image 
      if let imageURL = NSURL(string: "\(series.seriesimageurl)") { 
       if let image = imageCache.objectForKey(imageURL) as? UIImage { 
         cell.seriesImage.image = image 
       } else { 
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { 
         if let tvimageData = NSData(contentsOfURL: imageURL) { 
          let image = UIImage(data: tvimageData) 
          self.imageCache.setObject(image!, forKey: imageURL) 
           dispatch_async(dispatch_get_main_queue(), {() -> Void in 
            cell.seriesImage.image = nil 
            cell.seriesImage.image = image 
           }) 
         } 
        }) 
       } 
      } 
      cell.seriesLabel.text = "\(series.title)" 
     } 
    } else { 
     let series = seriesArray[indexPath.row] 
     do { 
      // set image 
      if let imageURL = NSURL(string: "\(series.seriesimageurl)") { 
       if let image = imageCache.objectForKey(imageURL) as? UIImage { 
         cell.seriesImage.image = image 
       } else { 
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { 
         if let tvimageData = NSData(contentsOfURL: imageURL) { 
          let image = UIImage(data: tvimageData) 
          self.imageCache.setObject(image!, forKey: imageURL) 
          dispatch_async(dispatch_get_main_queue(), {() -> Void in 
           cell.seriesImage.image = nil 
           cell.seriesImage.image = image 
          }) 
         } 
        }) 
       } 

      } 
      cell.seriesLabel.text = "\(series.title)" 
     } 
    } 
    cell.layer.shouldRasterize = true 
    cell.layer.rasterizationScale = UIScreen.mainScreen().scale 
    cell.prepareForReuse() 
    return cell 
} 

SeriesCollectionViewCell

class SeriesCollectionViewCell: UICollectionViewCell { 

@IBOutlet weak var seriesImage: UIImageView! 
@IBOutlet weak var seriesLabel: UILabel! 

} 
+0

Le celle vengono riutilizzate e si sta rimuovendo l'immagine in modo asincrono, quindi quando si riordina la cella e si recupera in modo asincrono una nuova immagine, il vecchio recupero è ancora in esecuzione. Devi gestirlo. Probabilmente il modo più semplice è usare qualcosa di SDWebImage che ha un'estensione su UIImageView che gestirà questo per te – Paulw11

+0

@ Paulw11: Sì, ho pensato che avesse a che fare con le richieste non cancellate correttamente o qualcosa del genere. Speravo di scrivere il codice usando UIKit visto che sto usando questa applicazione come esperienza di apprendimento e mi sento di fare affidamento su un framework esterno che sconfigge quello scopo dato che si tratta di una scorciatoia. In realtà ho analizzato Alamofire/AlamofireImage e SDWebImage, ma per qualche motivo dopo l'installazione dei pod non riesco a importare nei moduli (questo è un problema separato che non ho ancora capito). Lo userò se ne avrò bisogno, voglio solo provare a gestirlo prima con UIKit. – shadowofjazz

+0

Un altro approccio è di memorizzare l'URL dell'immagine come una proprietà della cella, quindi nella chiusura del completamento, controlla il valore della proprietà rispetto all'URL appena recuperato. Se non corrisponde, non impostare l'immagine poiché la cella è già stata riutilizzata. – Paulw11

risposta

9

È necessario capire come dequeue funzioni correttamente. Ci sono ottimi articoli per questo.

Riassumendo:

Per mantenere lo scorrimento morbidezza, dequeue è utilizzato che essenzialmente riutilizza cellule dopo un certo limite. Supponiamo che tu abbia 10 celle visibili in un tempo , probabilmente creerà solo 16-18 (3-4 sopra, 3-4 sotto, solo le stime approssimative di ), anche se potresti aver bisogno di 1000 celle.

Ora, quando si sta facendo questo-

let cell: SeriesCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! SeriesCollectionViewCell 

si sta riutilizzando una cella esistente da cellule già fatte. Ecco perché vedi vecchie immagini per qualche tempo e poi vedi una nuova immagine una volta caricata.

È necessario cancellare la vecchia immagine non appena si rimuove la cella.

cell.seriesImage.image = UIImage() //nil 

Sarà essenzialmente impostare un segnaposto vuoto in questo modo a immagine e non si scherza con imagecache essere pari a zero nel tuo caso.

che risolve questo problema-

Notevolmente, se si fa un rotolo lento o medio, un'immagine diversa mostrerà prima che l'immagine corretta è resa nella cella corretta (il testo dell'etichetta per le cellule sono sempre corretto, solo l'immagine è sempre disattivata).

Ora quando si assegna l'immagine per la cella corrente, è necessario controllare nuovamente, se l'immagine che si sta assegnando appartiene o meno a questa cella. È necessario verificarlo perché la vista dell'immagine a cui la si sta assegnando viene riutilizzata e potrebbe essere associata a una cella in un indicePath diverso da quello di quando è stata generata la richiesta di caricamento dell'immagine.

Quando si elimina la cella e si imposta la proprietà dell'immagine su zero, chiedere alla serieImage di ricordare l'indice corrente per te.

cell.seriesImage.indexPath = indexPath 

Successivamente, si assegna solo l'immagine, se imageView appartiene ancora a IndexPath precedentemente assegnato. Funziona al 100% con il riutilizzo delle celle.

if cell.seriesImage.indexPath == indexPath 
cell.seriesImage.image = image 

Potrebbe essere necessario prendere in considerazione l'impostazione di IndexPath nell'istanza UIImageView. Ecco qualcosa che ho preparato e utilizzare per un simile scenario- UIView-Additions

E 'disponibile sia in Objective-C & Swift.

+0

Quindi, dopo alcuni test, la cancellazione della vecchia immagine non appena ho rimosso la cella la cella sembra sconvolgere la memorizzazione nella cache dell'immagine. Risolve il problema delle immagini multiple mostrate in una cella durante lo scorrimento, ma rompe la memorizzazione nella cache delle immagini che erano già state spostate. Ad esempio, se si scorre indietro, le immagini sono ora nere e non verranno caricate anche con un segnale reloaddata() inviato tramite aggiornamento. Per quanto riguarda il controllo dell'immagine appartenente alla cella, puoi approfondire un po 'di più?Ho dato un'occhiata al file swift di UIView Additions ma non sono sicuro di come implementarlo data la struttura del mio codice. – shadowofjazz

+0

Quando si rimuove una cella, si assegna l'oggetto indexPath all'istanza di imageView, in un secondo momento, si controlla questo valore prima di assegnare l'immagine. Sto aggiornando la risposta ora. –

+0

Per quanto riguarda il ripristino delle immagini memorizzate nella cache che sta diventando nero, dubito fortemente che si tratti di un problema con lo stesso oggetto a cui si fa riferimento tramite imageCache e l'immagine. Diciamo che hai un'immagine, la hai memorizzata nella cache, ora cell.seriesImage stava puntando a questa istanza UIImage nella cache e abbiamo bisogno di impostarla manualmente su zero. Ciò imposta anche il nil per l'istanza memorizzata nella cache di imageCache perché entrambi puntano alla stessa istanza. Questo potrebbe sembrare un po 'hacky, prova a impostare UIImage() invece di nil, questo imposterà un'istanza vuota sull'immagine con la dimensione CGRectZero. In questo modo non avrai problemi con le istanze memorizzate nella cache. –

2

Implementare func prepareForReuse() nella classe cella personalizzata. E resetta la tua immagine nella funzione prepareForReuse(). quindi ripristinerà la vista dell'immagine prima di riutilizzare la cella.

+0

Puoi approfondire questo o chiarire? Quando reimposto l'immagine nel metodo "func prepareForReuse()" nella mia classe di celle personalizzate, tutte le immagini che vengono fatte scorrere sono nere (nil) quando si torna a quelle immagini. – shadowofjazz

+0

override func prepareForReuse() { super.prepareForReuse() yourImageView.image = UIImage() } –

+0

Grazie! Ci proverò questo fine settimana. – shadowofjazz

Problemi correlati