I'm downloading images using DropBox's API and displaying them in a Collection View. When the user scrolls, the image either disappears from the cell and another is loaded or a new image is reloaded and replaces the image in the cell. How can I prevent this from happening? I've tried using SDWebImage, this keeps the images in the right order but still the images disappear and reload each time they are scrolled off screen. Also, I'm downloading the images directly, not from a URL, I'd prefer to not have to write a work-a-round to be able to use SDWebImage.
I'd post a gif as example but my reputation is too low.
Any help would be welcomed :)
var filenames = [String]()
var selectedFolder = ""
// image cache
var imageCache = NSCache<NSString, UIImage>()
override func viewDidLoad() {
super.viewDidLoad()
getFileNames { (names, error) in
self.filenames = names
if error == nil {
self.collectionView?.reloadData()
print("Gathered filenames")
}
}
collectionView?.collectionViewLayout = gridLayout
collectionView?.reloadData()
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
}
func getFileNames(completion: #escaping (_ names: [String], _ error: Error?) -> Void) {
let client = DropboxClientsManager.authorizedClient!
client.files.listFolder(path: "\(selectedFolder)", recursive: false, includeMediaInfo: true, includeDeleted: false, includeHasExplicitSharedMembers: false).response { response, error in
var names = [String]()
if let result = response {
for entry in result.entries {
if entry.name.hasSuffix("jpg") {
names.append(entry.name)
}
}
} else {
print(error!)
}
completion(names, error as? Error)
}
}
func checkForNewFiles() {
getFileNames { (names, error) in
if names.count != self.filenames.count {
self.filenames = names
self.collectionView?.reloadData()
}
}
}
func downloadFiles(fileName: String, completion:#escaping (_ image: UIImage?, _ error: Error?) -> Void) {
if let cachedImage = imageCache.object(forKey: fileName as NSString) as UIImage? {
print("using a cached image")
completion(cachedImage, nil)
} else {
let client = DropboxClientsManager.authorizedClient!
client.files.download(path: "\(selectedFolder)\(fileName)").response { response, error in
if let theResponse = response {
let fileContents = theResponse.1
if let image = UIImage(data: fileContents) {
// resize the image here and setObject the resized Image to save it to cache.
// use resized image for completion as well
self.imageCache.setObject(image, forKey: fileName as NSString)
completion(image, nil) // completion(resizedImage, nil)
}
else {
completion(nil, error as! Error?)
}
} else if let error = error {
completion(nil, error as? Error)
}
}
.progress { progressData in
}
}
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.filenames.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! ImageCell
cell.backgroundColor = UIColor.lightGray
let fileName = self.filenames[indexPath.item]
let cellIndex = indexPath.item
self.downloadFiles(fileName: fileName) { (image, error) in
if cellIndex == indexPath.item {
cell.imageCellView.image = image
print("image download complete")
}
}
return cell
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
gridLayout.invalidateLayout()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
imageCache.removeAllObjects()
}
Because TableView's and CollectionView's use the
dequeueReusableCell(withReuseIdentifier: for indexPath:) function when you configure a new cell, what swift does under the table is use a cell that is out of the screen to help the memory of your phone and probably that cell already has a image set and you have to handle this case.
I suggest you to look at the method "prepareCellForReuse" in this case what I think you have to do is set the imageView.image atribute to nil.
I have pretty sure that it will solve your problem or give you the right direction, but if it doesn't work please tell me and I will try to help you.
Best results.
I fixed it. It required setting the cell image = nil in the cellForItemAt func and canceling the image request if the user scrolled the cell off screen before it was finished downloading.
Here's the new cellForItemAt code:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let fileId = indexPath.item
let fileName = self.filenames[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! ImageCell
cell.backgroundColor = UIColor.lightGray
if cell.request != nil {
print("request not nil; cancel ", fileName)
}
cell.request?.cancel()
cell.request = nil
cell.imageCellView.image = nil
print ("clear image ", fileId)
self.downloadFiles(fileId:fileId, fileName: fileName, cell:cell) { (image, error) in
guard let image = image else {
print("abort set image ", fileId)
return
}
cell.imageCellView.image = image
print ("download/cache: ", fileId)
}
return cell
}
Use SDWebImage and add a placeholder image :
cell.imageView.sd_setImage(with: URL(string: "http://www.domain.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))
I post this in case it may help someone.
I have a collection view (displayed as a vertical list) whose items are collection views (displayed as horizontal single-line grids). Images in the child-collection views were repeated when the list was scrolled.
I solved it by placing this in the class of the cells of the parent collection view.
override func prepareForReuse() {
collectionView.reloadData()
super.prepareForReuse()
}
Related
The image in the collection view cell is not updated when the image is downloaded from the server. The image gets updated when the collection view is scrolled.
Every section of the table view has a collection view. And table view cell has datasource for the collection view.
extension OffersCell: UICollectionViewDataSource,UICollectionViewDelegate{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photoViewModel.photos.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath)
(cell as! PhotoCell).imageView.contentMode = .scaleAspectFill
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let photo = self.photoViewModel.photos[indexPath.row]
(cell as! PhotoCell).imageView.image = UIImage(named: "dummyImage")
ImageDownloadManager.shared.downloadImage(photo, indexPath: indexPath) { (image, imageIndexPath, error) in
if let indexPathNew = imageIndexPath, indexPathNew == indexPath {
DispatchQueue.main.async {
(cell as! PhotoCell).imageView.image = image
}
}
}
}
}
Please find the image downloader class :
typealias ImageDownloadHandler = (_ image: UIImage?, _ indexPath: IndexPath?, _ error: Error?) -> Void
final class ImageDownloadManager {
private var completionHandler: ImageDownloadHandler?
lazy var imageDownloadQueue: OperationQueue = {
var queue = OperationQueue()
queue.name = "imageDownloadQueue"
queue.qualityOfService = .userInteractive
return queue
}()
let imageCache = NSCache<NSString, UIImage>()
static let shared = ImageDownloadManager()
private init () {}
func downloadImage(_ photo: Photos, indexPath: IndexPath?, handler: #escaping ImageDownloadHandler) {
self.completionHandler = handler
guard let url = photo.getImageURL() else {
return
}
if let cachedImage = imageCache.object(forKey: photo.id as NSString) {
self.completionHandler?(cachedImage, indexPath, nil)
} else {
let operation = CustomOperation(url: url, indexPath: indexPath)
if indexPath == nil {
}
operation.queuePriority = .high
operation.downloadHandler = { (image, indexPath, error) in
if let newImage = image {
self.imageCache.setObject(newImage, forKey: photo.id as NSString)
}
self.completionHandler?(image, indexPath, error)
}
imageDownloadQueue.addOperation(operation)
}
}
func cancelAll() {
imageDownloadQueue.cancelAllOperations()
}
}
After you downloaded the image, you execute the instruction (cell as! PhotoCell).imageView.image = image on the main thread. But this does not redisplay your collectionView cell.
Also, collectionView:willDisplayCell:forItemAtIndexPath: will normally not be called. The docs say
The collection view calls this method before adding a cell to its
content.
It is however called, when you scroll in the cell, i.e. when it becomes visible. This is there reason why your image is displayed after the cell is scrolled in.
So my suggestion is:
After downloading the image, update your collectionView data source
so that collectionView:cellForItemAtIndexPath: can configure the cell
with the image.
Call reloadItems(at:) with an array that contains only the index path of the updated cell.
It depends on how you define the class CustomOperation, but the problem seems to be in the method downloadImage of ImageDownloadManager where in the next line you set self.completionHandler = handler. Note that ImageDownloadManager is a singleton. This means that every operation you start replaces completionHandler of the singleton object with the new completion (I bet only the last cell was refreshed). The solution consists of elimination the property completionHandler and replacing the operation download handler with this
operation.downloadHandler = { (image, indexPath, error) in
if let newImage = image {
self.imageCache.setObject(newImage, forKey: photo.id as NSString)
}
handler(image, indexPath, error)
}
Note that it calls the handler of the context and not the stored property of the download manager
Here is a full working example with all the class and struct definitions. Adapt it as needed.
typealias ImageDownloadHandler = (_ image: UIImage?, _ indexPath: IndexPath?, _ error: Error?) -> Void
enum ImageDownloadError: Error {
case badDataURL
}
class CustomOperation: Operation {
var downloadHandler: (UIImage?, IndexPath?, Error?) -> () = { _,_,_ in }
private let url: URL
private let indexPath: IndexPath?
init(url: URL, indexPath: IndexPath?) {
self.url = url
self.indexPath = indexPath
}
override func main() {
guard let imageData = try? Data(contentsOf: self.url) else {
self.downloadHandler(nil, self.indexPath, ImageDownloadError.badDataURL)
return
}
let image = UIImage(data: imageData)
self.downloadHandler(image, self.indexPath, nil)
}
}
final class ImageDownloadManager {
private var completionHandler: ImageDownloadHandler?
lazy var imageDownloadQueue: OperationQueue = {
var queue = OperationQueue()
queue.name = "imageDownloadQueue"
queue.qualityOfService = .userInteractive
return queue
}()
let imageCache = NSCache<NSString, UIImage>()
static let shared = ImageDownloadManager()
private init () {}
func downloadImage(_ photo: Photos, indexPath: IndexPath?, handler: #escaping ImageDownloadHandler) {
//self.completionHandler = handler
guard let url = photo.getImageURL() else {
return
}
if let cachedImage = imageCache.object(forKey: photo.id as NSString) {
//self.completionHandler?(cachedImage, indexPath, nil)
handler(cachedImage, indexPath, nil)
} else {
let operation = CustomOperation(url: url, indexPath: indexPath)
if indexPath == nil {
}
operation.queuePriority = .high
operation.downloadHandler = { (image, indexPath, error) in
if let newImage = image {
self.imageCache.setObject(newImage, forKey: photo.id as NSString)
}
//self.completionHandler?(image, indexPath, error)
handler(image, indexPath, error)
}
imageDownloadQueue.addOperation(operation)
}
}
func cancelAll() {
imageDownloadQueue.cancelAllOperations()
}
}
-------------------------------------------------------
struct Photos {
let id: String
let url: URL
func getImageURL() -> URL? {
return self.url
}
}
struct PhotoViewModel {
let photos: [Photos]
}
class PhotoCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
}
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private let photoViewModel: PhotoViewModel = PhotoViewModel(
photos: [
Photos(
id: "kitty1",
url: URL(
string: "https://cdn.pixabay.com/photo/2019/06/18/11/23/cat-4282110_960_720.jpg"
)!
),
Photos(
id: "kitty2",
url: URL(
string: "https://cdn.pixabay.com/photo/2019/07/23/20/08/cat-4358536_960_720.jpg"
)!
),
Photos(
id: "kitty3",
url: URL(
string: "https://cdn.pixabay.com/photo/2016/09/28/13/15/kittens-1700474_960_720.jpg"
)!
)
]
)
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
collectionView.reloadData()
}
}
extension ViewController: UICollectionViewDataSource,UICollectionViewDelegate{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photoViewModel.photos.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath)
(cell as! PhotoCell).imageView.contentMode = .scaleAspectFill
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let photo = self.photoViewModel.photos[indexPath.row]
(cell as! PhotoCell).imageView.image = UIImage(named: "dummyImage")
ImageDownloadManager.shared.downloadImage(photo, indexPath: indexPath) { (image, imageIndexPath, error) in
if let indexPathNew = imageIndexPath, indexPathNew == indexPath {
DispatchQueue.main.async {
(cell as! PhotoCell).imageView.image = image
}
}
}
}
}
Yes, once image is downloaded is will not display unless collection view is scrolled as said by #Reinhard Männer
Instead you can go for the third-party SDKs(which fit your needs) for image downloading and caching in your app.
I will recommend to use Kingfisher SDK (developed in pure swift).
It is easy to use and integrate. it does lot of thing like async. downloading, caching(on memory or disk), built-in transition animation when setting images, etc. and it is popular too
For you'r problem it is one line code if you use Kingfisher SDK.
For eg.
To load image asynchronously you can use following in cellForRowAtItem: method.
let url = URL(string: "https://example.com/image.png")
imageView.kf.setImage(with: url)
What you all need to do is...
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as! PhotoCell
cell.imageView.contentMode = .scaleAspectFill
//I'm assuming photo is URL(in string) of Photo. if 'photo' is URL type then you can pass it directly in 'setImage' method.
let photo = self.photoViewModel.photos[indexPath.row]
let imgUrl = URL(string: photo)
//It will download image asynchronously and cache it for later use. If the image is failed to downloaded due to some issue then "dummyImage" will be set in image view.
cell.imageView.kf.setImage(with: imgUrl, placeholder: UIImage(named: "dummyImage"))
return cell
}
Here you can remove cell willDisplay: method.
The gifs I fetch from the Giphy API returns correctly and in fact loads properly to the uicollectionview using SwiftGif.
The issue only surfaces when I scroll immediately, the uicollectionview loads either duplicate gifs or gifs that are in the incorrect index. I understand this is probably a timing issue with the delay in rendering the gif and loading the gif to the cell.
Any guidance would be appreciated as asynchronous operations are something I'm still unfamiliar with..
Also any best practices for handling gifs would be appreciated if there are any flags in the code below, specifically to support speed/memory usage.
I've tried placing various checks like seeing if the initially passed gif url is the same at the point it's loaded, and also setting the image to nil every time cellForItemAt is fired, but to no avail. Couldn't find existing threads that clearly resolved this issue as well.
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var gifCollectionView: UICollectionView!
var gifUrls: [String] = []
var gifImages: [String: UIImage] = [:]
func fetchGiphs() {
let op = GiphyCore.shared.search("dogs", media: .sticker) { (response, error) in
guard error == nil else {
print("Giphy Fetch Error: ", error)
return
}
if let response = response, let data = response.data, let pagination = response.pagination {
for result in data {
if let urlStr = result.images?.downsized?.gifUrl {
self.gifUrls.append(urlStr)
}
}
if !self.gifUrls.isEmpty {
DispatchQueue.main.async {
self.gifCollectionView.reloadData()
}
}
} else {
print("No Results Found")
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return gifUrls.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! GifCell
let passedUrlString = gifUrls[indexPath.item]
cell.imageView.image = nil
if let image = gifImages[gifUrls[indexPath.item]] {
DispatchQueue.main.async {
cell.imageView.image = image
cell.activityIndicator.isHidden = true
}
} else {
cell.activityIndicator.isHidden = false
cell.activityIndicator.startAnimating()
DispatchQueue.global(qos: .default).async {
let gifImage = UIImage.gif(url: self.gifUrls[indexPath.item])
DispatchQueue.main.async {
if passedUrlString == self.gifUrls[indexPath.item] {
cell.activityIndicator.stopAnimating()
cell.activityIndicator.isHidden = true
cell.imageView.image = gifImage
self.gifImages[self.gifUrls[indexPath.item]] = gifImage
}
}
}
}
return cell
}
}
class GifCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
}
As you know, the cell may be reused when image loading completed.
You need to check if it is reused or not. Your passedUrlString == self.gifUrls[indexPath.item] does not work for this purpose.
Maybe, giving a unique ID for each cell would work:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! GifCell
let uniqueId = Int.random(in: Int.min...Int.max) //<-practically unique
cell.tag = uniqueId //<-
cell.imageView.image = nil
if let image = gifImages[gifUrls[indexPath.item]] {
cell.imageView.image = image
cell.activityIndicator.isHidden = true
} else {
cell.activityIndicator.isHidden = false
cell.activityIndicator.startAnimating()
DispatchQueue.global(qos: .default).async {
let gifImage = UIImage.gif(url: self.gifUrls[indexPath.item])
DispatchQueue.main.async {
if cell.tag == uniqueId { //<- check `cell.tag` is not changed
cell.activityIndicator.stopAnimating()
cell.activityIndicator.isHidden = true
cell.imageView.image = gifImage
self.gifImages[self.gifUrls[indexPath.item]] = gifImage
}
}
}
}
return cell
}
Assuming you are not using tag for other purposes.
Please try.
I am using UICollectionview to list some image as usual.
Code is simply getting data from api and update UICollectionView asynchronously. End of the API call code is updating data with self.collectionView?.reloadData().
Also when user scrolls to bottom of UICollectionView api call is triggered again and update photos array and collectionview data. But at this second action is not adding new cells to UICollectionView
Here is code:
class PhotoStreamViewController: UICollectionViewController {
var photos = [Photo]()
var pageIndex = 1
...
override func viewDidLoad() {
super.viewDidLoad()
allPhotos()
...
}
override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
pageIndex+=1
allPhotos()
}
func allPhotos() {
let headers: HTTPHeaders = [
"Authorization": "Client-ID APIKEY"
]
var json:JSON = JSON()
Alamofire.request("https://www.apiurl.com/photos?page=\(pageIndex)&per_page=15&order_by=latest",method: .get, parameters: nil , encoding: URLEncoding.default, headers: headers)
.responseJSON() { (response:DataResponse<Any>) in
// debugPrint(response)
switch response.result {
case .success(let value):
json = JSON(value)
for (_,subJson):(String, JSON) in json {
let image = UIImage(data: try! Data(contentsOf: URL(string: subJson["urls"]["small"].stringValue)!))
if let photo = Photo(userName: subJson["user"]["name"].stringValue,comment: "dummy",image: image!,location: "Location",thumb: "thumb") {
self.photos.append(photo)
}
}
self.collectionView?.reloadData()
case .failure(let error):
print(error)
}
}
}
}
extension PhotoStreamViewController : UICollectionViewDelegateFlowLayout{
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.photos.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AnnotatedPhotoCell", for: indexPath as IndexPath) as! AnnotatedPhotoCell
cell.photo = photos[indexPath.item]
return cell
}
}
You need to reload your collection view in main queue.Please try this.I think it's solve your problem
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
Problem solved after add this code under self.photos.append(photo)
if(self.pageIndex > 1){
let indexPath = IndexPath(item:self.photos.count-1, section: 0)
self.collectionView?.insertItems(at: [indexPath])
}
Also i had extension like :
extension PhotoStreamViewController : PinterestLayoutDelegate
after customize layoutattributes inside PinterestLayout it worked well
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
try this
if let layout = self.IBcollection?.collectionViewLayout as? PInterestLayout
{
layout.cache = []
layout.columnsYoffset = []
layout.contentSize = CGSize.zero
}
self.IBcollection.reloadData()
open PinterestLayout.
check prepare function. it containes below line. where cache is empty. whenever it reload it will get cache empty.
guard cache.isEmpty == true,let collectionView = collectionView else {return}
Replace above code with this:
guard let collectionView = collectionView else {return}
cache.removeAll() // Add this line while changing in array elements.
I want to asynchronously dispatch 8 image urls in a collection view. I have created a class for collection view cell and also made an outlet to imageview in it. Now I want to configure the imageview from main view controller. Here is the code
let reuseIdentifier = "PhotosCollectionViewCell" // also enter this string as the cell identifier in the storyboard
var items = ["1", "2", "3", "4", "5", "6", "7", "8"]
// tell the collection view how many cells to make
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.items.count
}
// make a cell for each cell index path
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PhotosCollectionViewCell
cell.imageView = imageView
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// handle tap events
print("You selected cell #\(indexPath.item)!")
}
func loadImage() {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
let urlString = "http://charcoaldesign.co.uk/AsyncImageView/Forest/IMG_0352.JPG"
let url = NSURL(string: urlString)
let data = NSData(contentsOfURL: url!)
dispatch_async(dispatch_get_main_queue(), {
self.imageView.image = UIImage(data: data!)
// self.items[0] = (data as? String)!
})
}
}
}
Here is a extension to make things more easy.
extension UIImageView {
func downloadImageFrom(link link:String, contentMode: UIViewContentMode) {
NSURLSession.sharedSession().dataTaskWithURL( NSURL(string:link)!, completionHandler: {
(data, response, error) -> Void in
dispatch_async(dispatch_get_main_queue()) {
self.contentMode = contentMode
if let data = data { self.image = UIImage(data: data) }
}
}).resume()
}
}
cell.imageView.downloadImageFrom(link: imageLinkArray[indexPath.row], contentMode: UIViewContentMode.ScaleAspectFit) //set your image from link array.
Also you can look below url for more help.
how to implement lazy loading of images in table view using swift
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PhotosCollectionViewCell
cell.imageView = imageView
loadImage(imageView, urlPAssed: urlString)
return cell
}
func loadImage(imageViewObj : UIImageView , urlPAssed : NSString) {
//let urlString = "http://charcoaldesign.co.uk/AsyncImageView/Forest/IMG_0352.JPG"
let url = NSURL(string: urlPAssed as String)
NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) in
if(error==nil)
{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
imageViewObj.image = UIImage(data: data!)
})
}
else
{
imageViewObj.image = UIImage(named: "dummy")
}
}.resume()
}
I am using DKImagePickerController to select multiple images, displaying them on my VC and them when I press a button they upload to Parse.
I load 7 images, but only the ones visible on the screen (Which is 4, as I am using a collectionView with a horizontal scroll) upload at the begining then if I scroll across to the end and upload again It uploads all 7 images. And if I scroll back and then forwards again the images seem to add up 7 at a time, so if I scrolled 4 times back and forth I would end up uploading 28 images when I only selected 7!!
I'm guessing it something to do with the way I add the images I have selected to the image Array which I am doing in the cellForItemAtIndexPath.
Here is my code below, if anyone has incounted this before or knows the issue you help is very much appreciated!
#IBOutlet weak var previewOrShareButton: UIBarButtonItem!
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var cancelButton: UIBarButtonItem!
var assets: [DKAsset]?
var images = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func selectImage(sender: AnyObject) {
let pickerController = DKImagePickerController()
pickerController.didSelectAssets = { (assets: [DKAsset]) in
print("didSelectAssets")
self.assets = assets
self.collectionView.reloadData()
}
pickerController.defaultSelectedAssets = self.assets
pickerController.maxSelectableCount = 7
pickerController.allowMultipleTypes = false
self.presentViewController(pickerController, animated: true) {}
}
#IBAction func postButton(sender: AnyObject) {
let post = PFObject(className: "Post")
post["username"] = PFUser.currentUser()!.username
for i in self.images.indices {
let imageData = self.images[i].lowestQualityJPEGNSData
let imageFile = PFFile(name: "image.JPEG", data: imageData)
post["imageArray\(i)"] = imageFile
}
post.saveInBackgroundWithBlock ({(success:Bool, error:NSError?) -> Void in
if error == nil {
}})
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return self.assets?.count ?? 0
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! BlogCollectionViewCell
let asset = self.assets![indexPath.row]
asset.fetchOriginalImageWithCompleteBlock { (image, info) -> Void in
cell.image.image = image
self.images.append(image!)
}
print(images)
return cell
}
Anything in your cellForRow will run every time a cell is scrolled to so it's fetching too many images. Ideally add the following to your viewDidLoad unless you need to the user to download the images then add this to a button:
for object in assets {
object.fetchOriginalImageWithCompleteBlock { (image, info) -> Void in
self.images.append(image!)
pickerController.reloadData()
}
}
That will load all of the images into the array and then you can populate your cells with the images:
cell.image = images[indexPath.row]
Yes you are doing it in wrong way, as you are fetching the images in cellForItemAtIndexPath and then adding the images in array in the same method. So when you scroll your UICollectionView then images will fetched and added to the array as cellForItemAtIndexPath will called when you scroll or reload your UICollectionView.
I will make some changes in your code, hope it will help you.
#IBAction func selectImage(sender: AnyObject) {
let pickerController = DKImagePickerController()
pickerController.didSelectAssets = { (assets: [DKAsset]) in
print("didSelectAssets")
assets.fetchOriginalImageWithCompleteBlock { (image, info) -> Void in
self.images.append(image!)
self.collectionView.reloadData()
}
}
pickerController.defaultSelectedAssets = self.assets
pickerController.maxSelectableCount = 7
pickerController.allowMultipleTypes = false
self.presentViewController(pickerController, animated: true) {}
}
and then cellForItemAtIndexPath will be
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! BlogCollectionViewCell
let asset = self.assets![indexPath.row]
cell.image.image = image
return cell
}