2016-06-13 4 views
7

Я новичок в программировании быстрого и iOS, но мне удалось создать в основном стабильный интерфейс для моего приложения в Xcode. CollectionView захватывает изображение и текст из массива словарей, созданных из файла csv на моем домашнем сетевом сервере. Файл резюме содержит данные в следующем формате (примечание, адреса были изменены, чтобы защитить лицензионные изображения):swift iOS - UICollectionView изображения смешались после быстрого прокрутки

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 

Проблема заключается в том, что при быстром пролистывании CollectionView, клетка захватит неправильное изображение. Заметно, что если вы выполняете медленный или средний прокрутки, другое изображение будет отображаться до того, как правильное изображение будет отображено в правильную ячейку (текст ярлыков для ячеек всегда правильный, только изображение всегда выключено). После несоответствия изображений правильной ячейке с меткой все остальные ячейки в CollectionView также будут отображаться неправильно.

E.g. В ячейке 1-9 будет отображаться Title1-9 с правильным изображением1-9 При медленной прокрутке, ячейки 19-27 будут отображать заголовок 19-27, на короткое время отобразится изображение 10-18, а затем отобразится правильное изображение 19-27. При быстром прокрутке огромного количества ячеек (например, от ячейки 1-9 до ячейки 90-99), ячейки 90-99 будут отображать заголовок 90-99, будет отображаться изображение 10-50ish, а затем будет неправильно оставаться на изображении 41- 50 (или около того). При дальнейшем прокрутке ячейки 100+ будут отображать правильное название, но будут отображаться только изображения из диапазона изображения 41-50.

Я думаю, что эта ошибка возникает либо из-за неправильного обращения с использованием ячеек, а к кешированию изображений, которые не обрабатываются должным образом, или того и другого. Это может быть и то, что я не вижу как начинающий программист iOS/swift. Я попытался реализовать запрос с модификатором завершения, но не могу заставить его работать должным образом с тем, как настроен мой код. Я был бы признателен за любую помощь в этом, а также за объяснение того, почему исправление работает так, как оно есть. Благодаря!

Соответствующий код приведен ниже.

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

Ячейки повторно используются, и вы отправляете изображение из асинхронно, поэтому, когда вы промахиваете ячейку и асинхронно извлекаете новое изображение, тогда старая выборка все еще работает. Вам нужно справиться с этим. Вероятно, самый простой способ - использовать что-то SDWebImage, которое имеет расширение на UIImageView, которое будет обрабатывать это для вас. – Paulw11

+0

@ Paulw11: Да, я полагал, что это связано с тем, что запросы не отменены должным образом или что-то в этом роде. Я надеялся написать код только с помощью UIKit, так как я использую это приложение в качестве учебного опыта, и я чувствую, что полагаюсь на внешнюю инфраструктуру, поражающую эту цель, поскольку это ярлык. Однако я действительно изучал Alamofire/AlamofireImage и SDWebImage, но по какой-то причине после установки контейнеров я не могу импортировать модули (это отдельная проблема, которую я не совсем понял). Я буду использовать это, если мне нужно, хотя, просто хочу сначала обработать его с помощью UIKit. – shadowofjazz

+0

Другой подход - сохранить URL-адрес изображения в качестве свойства вашей ячейки, а затем в закрытии завершения проверить значение свойства по URL-адресу, который вы только что выбрали. Если он не соответствует, не устанавливайте изображение, поскольку ячейка уже была повторно использована. – Paulw11

ответ

9

Вы должны понять, как Dequeue работает должным образом. Для этого есть очень хорошие статьи.

Резюмируя:

Для поддержания прокрутки гладкость, Dequeue используется, который по существу клетки после того, как повторно определенного предела. Скажем, у вас есть 10 видимых ячеек в времени, это, скорее всего, создаст 16-18 (3-4 выше, 3-4 ниже, только приблизительные оценки ), даже если вам может понадобиться 1000 ячеек.

Теперь, когда вы делаете this-

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

Вы повторно использовать существующую ячейку из уже сделанных ячеек. Вот почему вы видите старое изображение в течение некоторого времени, а затем вы видите новое изображение после его загрузки.

Вам необходимо очистить старое изображение, как только вы удалите ячейку.

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

Вы по существу установить пустой PlaceHolder таким образом в изображении и не связывайтесь с Imagecache быть ноль в вашем случае.

Это решает это проблемно

примечательным, если вы делаете медленный или средний свиток, другое изображение будет отображаться перед правильным изображением визуализируется в нужную ячейку (текст метки для ячеек всегда правильно, только изображение всегда выключено).

Теперь при назначении изображения для текущей ячейки вам нужно снова проверить, если присвоенное изображение принадлежит этой ячейке или нет. Вам нужно проверить это, потому что вид изображения, который вы назначаете, повторно используется, и он может быть связан с ячейкой в ​​indexPath, отличной от той, в которой был создан запрос загрузки изображения.

Когда вы деактивируете ячейку и установите свойство изображения на нуль, попросите seriesImage запомнить текущий indexPath для вас.

cell.seriesImage.indexPath = indexPath 

Позже вы присваиваете ему только изображение, если изображениеView по-прежнему принадлежит ранее присвоенному indexPath. Это работает на 100% при повторном использовании ячеек.

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

Возможно, вам придется рассмотреть возможность установки indexPath на экземпляр UIImageView. Вот что я подготовил и использовал для аналогичного сценария. UIView-Additions

Он доступен как для Objective-C & Swift.

+0

Итак, после некоторого тестирования очистка старого изображения, как только я вычеркнул ячейку, похоже, нарушит кэширование изображения. Он фиксирует проблему с несколькими изображениями, отображаемыми в ячейке во время прокрутки, но это прерывает кэширование изображений, которые уже прокручивались. Например, если я прокручиваю резервную копию, изображения теперь становятся черными и не будут загружаться даже с сигналом reloaddata(), отправленным с помощью обновления. Что касается проверки изображения, принадлежащего ячейке, можете ли вы расширить его немного больше?Я взглянул на ваш быстрый файл UIView Addditions, но я не уверен, как его реализовать, учитывая структуру моего кода. – shadowofjazz

+0

Когда вы деактивируете ячейку, вы назначаете экземпляр indexPath для экземпляра imageView, на более позднем этапе вы проверяете это значение перед назначением изображения. Я обновляю ответ сейчас. –

+0

Что касается сброса кэшированных изображений, то это черная проблема, я очень сомневаюсь, что проблема связана с тем же объектом, на который ссылаются через imageCache и изображение. Скажем, вы получили изображение, кэшировали его, теперь cell.seriesImage указывал на этот экземпляр UIImage внутри кеша, и нам нужно вручную установить его на нуль. Это также устанавливает нуль для кэшируемого экземпляра imageCache, поскольку оба указывают на один и тот же экземпляр. Это может показаться хаккой, попробуйте установить UIImage() вместо nil, это установит пустой экземпляр на изображении с размером CGRectZero. Таким образом, вы не будете связываться с существующими кэшированными экземплярами. –

2

Внесите func prepareForReuse() в свой собственный класс ячеек. И сбросьте свое изображение в функции prepareForReuse(). поэтому он будет сбросить представление изображения перед повторным использованием ячейки.

+0

Можете ли вы рассказать об этом или уточнить? Когда я сброшу изображение в методе func prepareForReuse() в моем пользовательском классе ячеек, все пропущенные изображения будут черными (ноль) при прокрутке назад к этим изображениям. – shadowofjazz

+0

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

+0

Спасибо! Я попробую это в эти выходные. – shadowofjazz