Bilal Drndo
Bilal Drndo

Reputation: 67

Images not showing in collectionView Swift

In console there is printing of empty imagesArray but I am downloading it in downloadImages function. And in simulator the images won't load

import UIKit
import PinterestLayout
import ProgressHUD

class MainViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {

    @IBOutlet weak var collectionView: UICollectionView!

    var postsArray = [Post]()

    var imagesArray = [UIImage]()

    override func viewDidLoad() {
        super.viewDidLoad()

       if let layout = collectionView.collectionViewLayout as? PinterestLayout {
            layout.delegate = self
        }

        collectionView.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

        loadItems()
        downloadPhoto()

    }

    func loadItems() {
        ref.child("Posts").observe(.childAdded) { (snapshot) in

            let snapshotValue = snapshot.value as! Dictionary<String,Any>

            if let title = snapshotValue["title"], let price = snapshotValue["price"], let downloadUrl = snapshotValue["downloadUrl"], let category = snapshotValue["category"], let senderUid = snapshotValue["senderUid"] {

                let post = Post()
                post.title = title as! String
                post.price = price as! String
                post.downloadUrl = downloadUrl as! String
                post.category = category as! String
                post.senderUid = senderUid as! String

                self.postsArray.append(post)

                self.collectionView.reloadData()

            }
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        tabBarController?.tabBar.shadowImage = UIImage()
        tabBarController?.tabBar.backgroundImage = UIImage()


    }

    func downloadPhoto(){
        DispatchQueue.global().async {
            self.imagesArray.removeAll() // this is the image array

            for i in 0..<self.postsArray.count {
                guard let url = URL(string: self.postsArray[i].downloadUrl) else {
                    continue
                }

                let group = DispatchGroup()

                print(url)
                print("-------GROUP ENTER-------")

                group.enter()
                URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
                    print(response?.suggestedFilename ?? url.lastPathComponent)

                    if let imgData = data, let image = UIImage(data: imgData) {
                        DispatchQueue.main.async() {
                            self.imagesArray.append(image)
                            self.collectionView.reloadData()
                        }
                    } else if let error = error {
                        print(error)
                    }

                    group.leave()
                }).resume()

                group.wait()
            }
        }
    }

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return imagesArray.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mainCell", for: indexPath) as! MainCollectionViewCell

        cell.imgView.downloadImage(from: self.postsArray[indexPath.row].downloadUrl)

        return cell
    }



}

extension MainViewController: PinterestLayoutDelegate {

    func collectionView(_ collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath: IndexPath) -> CGFloat {

        let image = imagesArray[indexPath.item]
        let height = image.size.height

        return height

    }
}

extension UIImageView {

    func downloadImage(from url: String){

        let urlRequest = URLRequest(url: URL(string: url)!)

        let task = URLSession.shared.dataTask(with: urlRequest) {
            (data,response,error) in

            if error != nil {
                print(error ?? error!)
                return
            }
            if let data = data {
                DispatchQueue.main.async {
                    self.image = UIImage(data: data)
                }
            }
        }
        task.resume()
    }

}

UPDATED CODE:

import UIKit
import PinterestLayout
import ProgressHUD
import Kingfisher

class MainViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {

    @IBOutlet weak var collectionView: UICollectionView!

    var postsArray = [Post]()

    var imagesArray = [UIImage]()

    override func viewDidLoad() {
        super.viewDidLoad()

       if let layout = collectionView.collectionViewLayout as? PinterestLayout {
            layout.delegate = self
        }

        collectionView.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

        loadItems()


        collectionView.reloadData()
    }

    func loadItems() {
        ref.child("Posts").observe(.childAdded) { (snapshot) in

            let snapshotValue = snapshot.value as! Dictionary<String,Any>

            if let title = snapshotValue["title"], let price = snapshotValue["price"], let downloadUrl = snapshotValue["downloadUrl"], let category = snapshotValue["category"], let senderUid = snapshotValue["senderUid"] {

                let post = Post()
                post.title = title as! String
                post.price = price as! String
                post.downloadUrl = downloadUrl as! String
                post.category = category as! String
                post.senderUid = senderUid as! String

                self.postsArray.append(post)
                self.downloadPhoto()

                print(self.imagesArray.count)

                self.collectionView.reloadData()

            }
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        tabBarController?.tabBar.shadowImage = UIImage()
        tabBarController?.tabBar.backgroundImage = UIImage()

    }

    func downloadPhoto(){
        DispatchQueue.global().async {
            self.imagesArray.removeAll() // this is the image array

            for i in 0..<self.postsArray.count {
                guard let url = URL(string: self.postsArray[i].downloadUrl) else {
                    continue
                }

                let group = DispatchGroup()

                print(url)
                print("-------GROUP ENTER-------")

                group.enter()
                URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
                    print(response?.suggestedFilename ?? url.lastPathComponent)

                    if let imgData = data, let image = UIImage(data: imgData) {
                        DispatchQueue.main.async() {
                            self.imagesArray.append(image)
                            let post = Post()
                            post.image = image
                            self.collectionView.reloadData()
                        }
                    } else if let error = error {
                        print(error)
                    }
                    group.leave()
                }).resume()

                group.wait()
            }

        }
    }

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return postsArray.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mainCell", for: indexPath) as! MainCollectionViewCell

        let resource = ImageResource(downloadURL: URL(string: postsArray[indexPath.row].downloadUrl)!, cacheKey: postsArray[indexPath.row].downloadUrl)
        cell.imgView.kf.setImage(with: resource)

        return cell
    }



}

extension MainViewController: PinterestLayoutDelegate {

    func collectionView(_ collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath: IndexPath) -> CGFloat {

        let image = imagesArray[indexPath.row]
        let height = image.size.height / 6

        return height

    }
}
//AND POST CLASS

import UIKit

    class Post {
    var title : String = ""
    var category : String = ""
    var downloadUrl : String = ""
    var price : String = ""
    var senderUid : String = ""
    var image = UIImage()
}

Upvotes: 0

Views: 863

Answers (2)

Scriptable
Scriptable

Reputation: 19750

As Sh_Khan said, you need to call downloadPhoto after loadItems has completed, otherwise there wont be any posts to loop over.

Also, a few points to consider here...

  1. You are not reloading any cells once the image download has completed (downloadPhoto)
  2. You are not caching images, so you will end up downloading images often. Once you scroll your collectionView and cells get reused you will download the same images, again.
  3. You are not using DispatchGroup effectively here (in downloadPhoto anyway), you appear to be downloading one image at a time (or trying to), not taking advantage of parallel downloads. If you intend to do this, use a serial queue. But this will slow down loading images considerably.

I prefer to use KingFisher for downloading and caching images, the library already manages most of this for you and leaves you to focus on your app.

If you dont want to use a library, something like this should help...

var imageCache = [String: UIImage]()

func downloadImage(from url: String){

    if let image = imageCache[url] as? UIImage {
        self.image = image
        return
    }

    let urlRequest = URLRequest(url: URL(string: url)!)

    let task = URLSession.shared.dataTask(with: urlRequest) {
        (data,response,error) in

        if error != nil {
            print(error ?? error!)
            return
        }

        if let data = data {
            DispatchQueue.main.async {
                let image = UIImage(data: data)
                imageCache[url] = image
                self.image = image
            }
        }
    }

    task.resume()
}

Kingfisher example:

imageView.kf.setImage(with: url, completionHandler: { 
    (image, error, cacheType, imageUrl) in
    // image: Image? `nil` means failed
    // error: NSError? non-`nil` means failed
    // cacheType: CacheType
    //                  .none - Just downloaded
    //                  .memory - Got from memory cache
    //                  .disk - Got from disk cache
    // imageUrl: URL of the image
})

so have a postsArray and images array

var postsArray = [Post]()

var images = [String: UIImage]() // dictionary, maps url to image

Then when you receive a Post:

let post = Post()
post.title = title as! String
post.price = price as! String
post.downloadUrl = downloadUrl as! String
post.category = category as! String
post.senderUid = senderUid as! String

self.postsArray.append(post)
imageView.kf.setImage(with: url, completionHandler: { (image, error, cacheType, imageUrl) in
    // check image is not nil etc
    images[url] = image
    collectionView.reloadData()
}

CellForRowAt:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mainCell", for: indexPath) as! MainCollectionViewCell

    cell.imgView.image = images[postsArray[indexPath.row].downloadUrl]
    return cell
}

Upvotes: 0

Shehata Gamal
Shehata Gamal

Reputation: 100503

You need to call

downloadPhoto()

inside

loadItems()

as both are asynchronous , here

self.postsArray.append(post)
downloadPhoto()

Note: I recommend having a cache to check before downloading the image or better use SDWebImage

Upvotes: 1

Related Questions