1

I've this annoying bug in my app that every time I load the UICollectionView controller, it duplicates the cells.

So this is the when the collection view loads for the first time:

enter image description here

I've 2 view controller so this is the second view controller. If I unwind back to the first view controller which only has 1 button to the 2nd view controller and segue again to the second view controller which triggers the reloadData function, this happens:

enter image description here

I'm not really sure why this is happening, I am using Todd Kramer's UICollectionView with cached images: http://www.tekramer.com/downloading-images-asynchronously-in-swift-with-alamofire/ The only difference is that I'm loading the image urls and data from JSON asynchronously instead of statically defining in a model or plist as the tutorial does.

Here are the classes with changes I made in the code:

import UIKit
import SwiftyJSON

private let PhotoCollectionViewCellIdentifier = "cell"

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {

@IBOutlet weak var collectionView: UICollectionView!

var index: NSIndexPath!

var names = [String]()
var imgsUrl = [String]()

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    let photo = PhotosDataManager()
    let api = Api()

    api.loadJsonData(self.names, imgsUrl: self.imgsUrl, batch: "2016", dept: "dafa") { names, imgsUrl in
        self.names = names
        self.imgsUrl = imgsUrl
        photo.allPhotos(self.names, imgUrls: self.imgsUrl)
        self.collectionView.reloadData()
    }
}

@IBAction func button(sender: UIButton) {
    NSNotificationCenter.defaultCenter().postNotificationName("alert1", object: self, userInfo: nil)
}

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

    return PhotosDataManager.sharedManager.allPhotos(self.names, imgUrls: self.imgsUrl).count
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(PhotoCollectionViewCellIdentifier, forIndexPath: indexPath) as! CollectionViewCell
    dispatch_async(dispatch_get_main_queue(), {
        cell.configure(self.glacierScenicAtIndex(indexPath))
    })

    return cell
}

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
    PhotosDataManager.sharedManager.purgeCache()
    self.performSegueWithIdentifier("previousViewController", sender: self)

}

func glacierScenicAtIndex(indexPath: NSIndexPath) -> GlacierScenic {
    let photos = PhotosDataManager.sharedManager.allPhotos(self.names, imgUrls: self.imgsUrl)
    return photos[indexPath.row]
}

}

class PhotosDataManager {

static let sharedManager = PhotosDataManager()
private var photos = [GlacierScenic]()

let decoder = ImageDecoder()
let photoCache = AutoPurgingImageCache(
    memoryCapacity: 100 * 1024 * 1024,
    preferredMemoryUsageAfterPurge: 60 * 1024 * 1024
)

func allPhotos(names: [String], imgUrls: [String]) -> [GlacierScenic] {

    var glacierScenic: GlacierScenic!

    for i in 0 ..< names.count {
        glacierScenic = GlacierScenic(name: names[i], photoURLString: imgUrls[i])
        photos.append(glacierScenic)
    }

    return photos
}

func getNetworkImage(urlString: String, completion: (UIImage -> Void)) -> (ImageRequest) {
    let queue = decoder.queue.underlyingQueue
    let request = Alamofire.request(.GET, urlString)
    let imageRequest = ImageRequest(request: request)
    imageRequest.request.response(
        queue: queue,
        responseSerializer: Request.imageResponseSerializer(),
        completionHandler: { response in
            guard let image = response.result.value else {
                return
            }
            let decodeOperation = self.decodeImage(image) { image in
                completion(image)
                self.cacheImage(image, urlString: urlString)
            }
            imageRequest.decodeOperation = decodeOperation
        }
    )
    return imageRequest
}

func decodeImage(image: UIImage, completion: (UIImage -> Void)) -> DecodeOperation {
    let decodeOperation = DecodeOperation(image: image, decoder: self.decoder, completion: completion)
    self.decoder.queue.addOperation(decodeOperation)
    return decodeOperation
}

func cacheImage(image: Image, urlString: String) {
    photoCache.addImage(image, withIdentifier: urlString)
}

func cachedImage(urlString: String) -> Image? {
    return photoCache.imageWithIdentifier(urlString)
}

func purgeCache() {
//        photoCache.removeAllImages()
    print("memory used: \(photoCache.memoryUsage)")
}

}

class PhotosDataManager {

static let sharedManager = PhotosDataManager()
private var photos = [GlacierScenic]()

let decoder = ImageDecoder()
let photoCache = AutoPurgingImageCache(
    memoryCapacity: 100 * 1024 * 1024,
    preferredMemoryUsageAfterPurge: 60 * 1024 * 1024
)

func allPhotos(names: [String], imgUrls: [String]) -> [GlacierScenic] {

    var glacierScenic: GlacierScenic!

    for i in 0 ..< names.count {
        glacierScenic = GlacierScenic(name: names[i], photoURLString: imgUrls[i])
        photos.append(glacierScenic)
    }

    return photos
}

func getNetworkImage(urlString: String, completion: (UIImage -> Void)) -> (ImageRequest) {
    let queue = decoder.queue.underlyingQueue
    let request = Alamofire.request(.GET, urlString)
    let imageRequest = ImageRequest(request: request)
    imageRequest.request.response(
        queue: queue,
        responseSerializer: Request.imageResponseSerializer(),
        completionHandler: { response in
            guard let image = response.result.value else {
                return
            }
            let decodeOperation = self.decodeImage(image) { image in
                completion(image)
                self.cacheImage(image, urlString: urlString)
            }
            imageRequest.decodeOperation = decodeOperation
        }
    )
    return imageRequest
}

func decodeImage(image: UIImage, completion: (UIImage -> Void)) -> DecodeOperation {
    let decodeOperation = DecodeOperation(image: image, decoder: self.decoder, completion: completion)
    self.decoder.queue.addOperation(decodeOperation)
    return decodeOperation
}

func cacheImage(image: Image, urlString: String) {
    photoCache.addImage(image, withIdentifier: urlString)
}

func cachedImage(urlString: String) -> Image? {
    return photoCache.imageWithIdentifier(urlString)
}

func purgeCache() {
//        photoCache.removeAllImages()
    print("memory used: \(photoCache.memoryUsage)")
}

}
Ekta Padaliya
  • 5,743
  • 3
  • 39
  • 51
Renboy
  • 451
  • 4
  • 21

1 Answers1

2

ISSUE

In PhotosDataManager => You are using sharedManager reference, which saves object reference in static reference and keeps reusing it, in all segues navigation, and thus your allPhotos method, keep adding data in old array.

 static let sharedManager = PhotosDataManager()

ANOTHER ISSUE

Don't Call allPhotos method in numberOfItemsInSection, as it gets called multiple number of times, which might append the data multiple number of times


SOLUTION

In your method:

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

     //USE Instantiated reference
    return photos.getPhotosCount()
}

Instead, inside your class class PhotosDataManager

class PhotosDataManager {

  func getPhotosCount(){

    return photos.count
  }
}
gunjot singh
  • 2,578
  • 20
  • 28
  • It works when returning allPhotos.count but not with getPhotosCount. – Renboy Jun 24 '16 at 05:23
  • Ok it works now when I called allPhotos before returning getPhotosCount. Thank you. – Renboy Jun 24 '16 at 05:29
  • Can I have another question though, if it's okay? – Renboy Jun 24 '16 at 05:30
  • Is there a way to remove all those images from the cache? Because I wanted to load another set of images, for example, replacing batman heads with superman photos. What happens is that the newly loaded photos just add up because of the cached images. I tried using photoCache.removeAll() but it doesn't do anything. – Renboy Jun 24 '16 at 05:38
  • 1
    Yes, either add other method to class PhotosDataManager => func clearPhotos(){ return photos.removeAll() } or remove private from photos variable and clear it directly. -------------- Also reload collectionView after clearing all photos – gunjot singh Jun 24 '16 at 05:40