Search code examples
iosswiftfirebaseuicollectionviewuiimageview

loading imageviews of collectionview after the viewcontroller is loaded


So I am building an app in swift where a user can upload a product with pictures. The upload works perfectly but the problem I'm having is the download of the pictures. The user gets an overview of all the products and clicks on them and that sends him to a detailed overview of that product. In that overview, there is a collectionview with all the images (that is the goal). My problem is that the picture are loaded too slow for the collection view and don't appear on the screen. I was wondering if there was an option to give the image view in a function and assign images to them and that the pictures will load one by one after the screen did already load?

This is the function that I am having right now to load my images:

    func getPictures(imageStrings: [String], imageViews: [UIImageView]){
        let storage = Storage.storage();
        for index in 0...imageStrings.count - 1 {
            let gsReference = storage.reference(forURL: imageStrings[index])
            gsReference.getData(maxSize: 15 * 1024 * 1024) { data, error in
                    if let error = error {
                            // Uh-oh, an error occurred!
                            print(error)
                            return
                    } else {
                        DispatchQueue.main.async {
                        imageViews[index].image = UIImage(data: data!)
                        }
                        print(UIImage(data: data!) as Any)
                    }
            }
        }
    }

this is the viewcontroller where my collection view is active

import MessageUI
import FirebaseStorage

class ProductDetailViewController: UIViewController, MFMailComposeViewControllerDelegate, UINavigationControllerDelegate, UICollectionViewDelegate, UICollectionViewDataSource   {


    @IBOutlet var TitleLable: UILabel!
    @IBOutlet var PriceLable: UILabel!
    @IBOutlet var ProductDate: UILabel!
    @IBOutlet var UsernameLable: UILabel!
    @IBOutlet var LocationLable: UILabel!
    @IBOutlet var Userdate: UILabel!
    @IBOutlet var ReserveerButton: UIButton!
    @IBOutlet var DescriptionTextView: UITextView!
    @IBOutlet var collectionView: UICollectionView!

    var titleLable = String()
    var priceLable = String()
    var productDate = String()
    var descriptionLable = String()
    var userId = String()
    var usernameLable = String()
    var locationLable = String()
    var userdate = String()
    var email = String()
    var imageStrings = [String]()
    var images = [UIImage]()
    var dbhelper = DBHelper()

    override func viewDidLoad() {
        super.viewDidLoad()
        ReserveerButton.layer.cornerRadius = 20
        loadUser(id: userId)
        TitleLable.text = titleLable
        PriceLable.text = priceLable
        ProductDate.text = productDate
        DescriptionTextView.text = descriptionLable
        UsernameLable.text = usernameLable
        loadImages()    

        // Do any additional setup after loading the view.
    }

    private func loadUser(id: String){
        let dbhelper = DBHelper()
        dbhelper.getUserbyUserID(id: id){ success in
            if(dbhelper.users[0].Bedrijf != "NULL"){
                self.UsernameLable.text = dbhelper.users[0].Bedrijf
            } else {
                self.UsernameLable.text = dbhelper.users[0].Voornaam + " " + dbhelper.users[0].Familienaam
            }
            self.Userdate.text = dbhelper.users[0].Added_on
            self.LocationLable.text = dbhelper.users[0].Stad
            self.email = dbhelper.users[0].Email
        }
    }

    @IBAction func ReserveerProduct(_ sender: Any) {
        if !MFMailComposeViewController.canSendMail() {
            print("Mail services are not available")
            return
        }

        let composeVC = MFMailComposeViewController()
        composeVC.mailComposeDelegate = self

        // Configure the fields of the interface.
        composeVC.setToRecipients([self.email])
        composeVC.setSubject(self.titleLable)
        composeVC.setMessageBody("Beste " + self.usernameLable + ", \n\nIk zou graag het zoekertje " + self.titleLable + " reserveren.  Is dit nog steeds mogelijk? \n\nMet vriendelijke groet" , isHTML: false)

        // Present the view controller modally.
        self.present(composeVC, animated: true, completion: nil)

    }

    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {

        switch result.rawValue {
        case MFMailComposeResult.cancelled.rawValue :
            print("Cancelled")

        case MFMailComposeResult.failed.rawValue :
            print("Failed")

        case MFMailComposeResult.saved.rawValue :
            print("Saved")

        case MFMailComposeResult.sent.rawValue :
            print("Sent")
        default: break
        }
        self.dismiss(animated: true, completion: nil)
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return imageStrings.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCollectionViewCell", for: indexPath) as! ImageCollectionViewCell
        cell.contentMode = .scaleAspectFill
        return cell
    }

    func loadImages(){
        let dbhelper = DBHelper()
        //dbhelper.getPictures2(imageStrings: self.imageStrings, imageViews: self.images)
    }

}

Hope I can finally solve this problem! Kind regards B.


Solution

  • I suppose the problem you are facing is that you do not update your collection view after loading the images.

    Firstly, your getPictures(...) method expects an array of UIImageViews. But as a matter of fact, you cannot pass them to the method because they are dynamically created in your collectionView. Instead you should return the images, once they are loaded. The problem is that the images are loaded asynchronously. This means that you need to use a completion handler.

    func getPictures(imageStrings: [String], completionHandler: @escaping (UIImage) -> ()) {
        let storage = Storage.storage();
        for index in 0...imageStrings.count - 1 {
            let gsReference = storage.reference(forURL: imageStrings[index])
            gsReference.getData(maxSize: 15 * 1024 * 1024) { data, error in
                    if let error = error {
                            // Uh-oh, an error occurred!
                            print(error)
                            return
                    } else {
                        completionHandler(UIImage(data: data!))
                        print(UIImage(data: data!) as Any)
                    }
            }
        }
    }
    

    Then you need to modify your loadImages() method:

        func loadImages() {
            let dbhelper = DBHelper()
            dbhelpergetPictures(imageStrings: [""]) { (loadedImage) in
                DispatchQueue.main.async {
                    self.images.append(loadedImage)
                    self.collectionView.reloadData()
                }
            }
        }
    

    After that you need to modify your collectionView(numberOfItemsInSection) method in order to make the number of displayed cells equal to the number of loaded images:

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return images.count
    }
    

    And finally, you need to actually display the images in the UIImageViews inside your collection view:

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCollectionViewCell", for: indexPath) as! ImageCollectionViewCell
        cell.contentMode = .scaleAspectFill
        cell.imageView.image = images[indexPath.row]
        return cell
    }
    

    If you have any questions about this, just ask them in the comments.

    (PS: A well-intentioned advice: take a look at the swift naming conventions (here is a good style guide). All names, except from classes, should always start with a lowercase letter and should describe their purpose. Example: var TitleLable: UILabel! -> var titleLable: UILabel! and: var titleLable = String() -> var title = String() This will make your code way more understandable!)