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.
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.
I want to load collectionView Cell repeatedly.
I want to make a dynamic cell, and when 20 cells are loaded, I want to make 40 cells by loading 20 underneath again.
API.requestBookCategory(bookCategory: 9, bookAddPoint: 0, completionHandler: handleBooksCategory(books:error:))
func handleBooksCategory(books: Books?, error: Error?) {
self.booksCategory = books
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
This code retrieves information from 20 books in bookCategory 9.
When importing 1 to 20 book information, insert bookAddPoint 0.
After that, we insert book information into the bookCatagory variable and use bookCategory to display the book information data in the cell.
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return booksCategory?.books.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView
.dequeueReusableCell(withReuseIdentifier: "MainBookCell", for: indexPath) as! MainBookCell
func handleImageResponse() {
guard let imageURL = URL(string: booksCategory?.books[indexPath.row].bookImage ?? "") else {
return
}
API.requestImageFile(url: imageURL, completionHandler: handleImageFileResponse(image:error:))
}
func handleImageFileResponse(image: UIImage?, error: Error?) {
DispatchQueue.main.async {
cell.bookImageView.image = image
}
}
cell.bookTitleLabel.text = booksCategory?.books[indexPath.row].bookTitle
cell.bookWriterLabel.text = booksCategory?.books[indexPath.row].authorName
handleImageResponse()
return cell
}
}
I go through the process up to here, and a cell displaying 20 book information is created.
If a cell showing the 20th book information is created,
API.requestBookCategory (bookCategory: 9, bookAddPoint: 20, completionHandler: handleBooksCategory (books: error :))
I want to run a code that modifies bookAddPoint from 0 to 20, like the code above, to create a cell that displays the 21st to 40th book information. What should I do?
in viewControoller create a variables
var bookAddPoint = 0
var pageLength = 20
func callNextPageData(){
bookAddPoint = booksCategory?.books.count + 1
API.requestBookCategory(bookCategory: 9, bookAddPoint: bookAddPoint, completionHandler: handleBooksCategory(books:error:))
}
change
func handleBooksCategory(books: Books?, error: Error?) {
for book in books{
self.booksCategory.appen(book)
}
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
Change cellforItem
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView
.dequeueReusableCell(withReuseIdentifier: "MainBookCell", for: indexPath) as! MainBookCell
func handleImageResponse() {
guard let imageURL = URL(string: booksCategory?.books[indexPath.row].bookImage ?? "") else {
return
}
API.requestImageFile(url: imageURL, completionHandler: handleImageFileResponse(image:error:))
}
func handleImageFileResponse(image: UIImage?, error: Error?) {
DispatchQueue.main.async {
cell.bookImageView.image = image
}
}
cell.bookTitleLabel.text = booksCategory?.books[indexPath.row].bookTitle
cell.bookWriterLabel.text = booksCategory?.books[indexPath.row].authorName
handleImageResponse()
if indexPath.row == self.booksCategory.count-3 // it will load records before you reach end point automatically you can increase or decrease number
{
callNextPageData()
}
return cell
}
NOTE: if you want to do this on load more button just call callNextPageData() in button action or in tableView footer actions.
My didSelectItemAt method is not being called and nothing is being printed into the console. I have user interaction turned on and I still can not get it to print out anything. I am not sure if my custom PinterestStyle Layout is causing this or if I am missing something. The ultimate goal would be to segue into a detail view controller showing the profile page of the cell selected. I will do that using prepareForSegue however I still can't even get it to print out the name of the cell when tapped.
class PagesCollectionViewController: UICollectionViewController, firebaseHelperDelegate {
var storageRef: StorageReference!{
return Storage.storage().reference()
}
var usersList = [String]()
var authService : FirebaseHelper!
var userArray : [Users] = []
var images: [UIImage] = []
var names: [String] = []
override func viewWillAppear(_ animated: Bool) {
if Global.Location != "" && Global.Location != nil
{
usersList = Global.usersListSent
print(usersList)
self.authService.ListOfUserByLocation(locationName: Global.Location, type: .ListByLocation)
}
}
override func viewDidLoad() {
self.collectionView?.allowsSelection = true
self.collectionView?.isUserInteractionEnabled = true
super.viewDidLoad()
self.authService = FirebaseHelper(viewController: self)
self.authService.delegate = self
setupCollectionViewInsets()
setupLayout()
}
private func setupCollectionViewInsets() {
collectionView!.backgroundColor = .white
collectionView!.contentInset = UIEdgeInsets(
top: 20,
left: 5,
bottom: 49,
right: 5
)
}
private func setupLayout() {
let layout: PinterestLayout = {
if let layout = collectionViewLayout as? PinterestLayout {
return layout
}
let layout = PinterestLayout()
collectionView?.collectionViewLayout = layout
return layout
}()
layout.delegate = self
layout.cellPadding = 5
layout.numberOfColumns = 2
}
func firebaseCallCompleted(data: AnyObject?, isSuccess: Bool, error: Error?, type: FirebaseCallType) {
if(type == .ListByLocation) {
if(isSuccess) {
self.userArray.removeAll()
self.images.removeAll()
self.images.removeAll()
if(data != nil) {
let dataDict = data as! NSDictionary
let keyArray = dataDict.allKeys
for i in 0 ..< keyArray.count {
var dict = NSDictionary()
dict = dataDict.object(forKey: keyArray[i]) as! NSDictionary
self.userArray.append(Users.init(data: dict))
}
}
self.collectionView?.reloadData()
}
else {
print(error?.localizedDescription)
SVProgressHUD.dismiss()
}
}
}
}
extension PagesCollectionViewController {
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return userArray.count
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(userArray[indexPath.row].name)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: "PagesCollectionViewCell",
for: indexPath) as! PagesCollectionViewCell
cell.nameLabel.text = userArray[indexPath.row].name
if let imageOld = URL(string: userArray[indexPath.row].photoURL){
cell.photo.sd_setImage(
with: imageOld,
placeholderImage: nil,
options: [.continueInBackground, .progressiveDownload]
)
}
return cell
}
}
extension PagesCollectionViewController : PinterestLayoutDelegate {
func collectionView(collectionView: UICollectionView,
heightForImageAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat {
var image: UIImage?
let url = URL(string: userArray[indexPath.row].photoURL)
let data = try? Data(contentsOf: url!)
image = UIImage(data: data!)
return (image?.height(forWidth: withWidth))!
}
func collectionView(collectionView: UICollectionView,
heightForAnnotationAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat {
return 30
}
}
Check 2 conditions:-
Make sure you have set delegate to UICollectionView
Make sure Content in PageCollectionCell like image having no user interaction enabled. If image user interaction is enabled then didSelectItemAt will not call.
as Manish Mahajan said a quick fix would be:
in cellForItemAt func set contentView as not clickable
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.contentView.isUserInteractionEnabled = false
return cell
}
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()
}
I'm using alamofire to get url image from json file, and want to display the image I get from json to imageview in cell. I'm new to swift and swift networking.
My code on MainCollectionViewController:
private let reuseIdentifier = "Cell"
class MainCollectionViewController: UICollectionViewController {
var result:String = ""
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return 1
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! MainCollectionViewCell
Alamofire.request(.POST, "URL That Contain JSON").responseJSON { response in
if let value = response.result.value {
let json = JSON(value)
let data = json["data"].arrayValue
self.result = data[0]["image"].stringValue
print(self.result)
}
}
let imageName = (result)
cell.mainImageView.image = UIImage(named:imageName)
return cell
}
And the image outlet is in MainCollectionViewCell:
class MainCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var mainImageView: UIImageView!
}
The build succeded but the image does not appear, it shows cell with no image inside.
Alamofire is still requesting for JSON but you have your UIImageView updated before the response arrives. Before Alamofire can come back with response, your code will try to update UIImageView with result which has not yet been received making your Result string still an empty string just like you declared on top.That is why you are getting empty UIImageView.
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! MainCollectionViewCell
Alamofire.request(.POST, "URL That Contain JSON").responseJSON { response in
if let value = response.result.value {
let json = JSON(value)
let data = json["data"].arrayValue
self.result = data[0]["image"].stringValue
print(self.result)
let imageName = (result)
cell.mainImageView.image = UIImage(named:imageName)
}
}
return cell
}
It is however not a popular practice to make load request when cell is updating.
Responce running in diferent thread so at that time "result" doesn't have data. try to load after the responce will solve your problem .
override func viewDidLoad() {
super.viewDidLoad()
getImage()
}
func getImage()
{
Alamofire.request(.POST, "URL That Contain JSON").responseJSON { response in
if let value = response.result.value {
let json = JSON(value)
let data = json["data"].arrayValue
self.result = data[0]["image"].stringValue
print(self.result)
}
//reload collection view
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! MainCollectionViewCell
let decodedData = NSData(base64EncodedString: result, options: NSDataBase64DecodingOptions(rawValue: 0))
let decodedimage = UIImage(data: decodedData!)
cell.mainImageView.image = decodedimage
return cell
}
In your code you didn't register your nib.
self.collectionView.registerNib(UINib(nibName: "MainCollectionViewCell", bundle: nil), forCellReuseIdentifier: "MainCollectionViewCell")
Put this code on ViewDidLoad
you are just getting image url not image from the server so firstly you should be download image then set in mainImageView.