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.
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!)