Renboy Renboy - 5 months ago 30
iOS Question

Swift 2 reloadData adds more cells to the UICollectionView

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

}

Answer

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
  }
}
Comments