1
votes

I have a problem retrieving all the data from firebase firestore (which keeps track of some user info, i.e. title, description and imageURL), then downloading the image from firebaseStorage, creating an object called Cella(title: String, description: String, image: UIImage). This process should happen in a loop, which then creates an object for every user, and returns an array of Cella objects to be passed to another ViewController and displayed on its tableView.

I've tried fixing the completion handlers because I thought they were the problem, and then I've added an IBAction which gets triggered when the segue is performed.

ViewController where I try to retrieve the data (notice that it is inside a Navigation View Controller, and the segue is the only one, which gets triggered when that button gets pressed).

class ViewController: UIViewController {

let firestoreUsersReference = Firestore.firestore().collection("users")
let storageReference = Storage.storage()

var cellaObjects : [Cella] = []


override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    super.prepare(for: segue, sender: sender)

    getDocumentsFromFirestore(firestoreReference: firestoreUsersReference) { (cellaArray) in
        self.cellaObjects = cellaArray
    }

}

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

}

func getImagesDownloaded(reference: StorageReference, completion: @escaping (UIImage?,Error?)->()) {
    reference.getData(maxSize: 10*1024*1024) { (data, error) in
        guard error == nil, let data = data else {
            completion(nil,error)
            return
        }
        guard let image = UIImage(data: data) else {
            completion(nil, FirebaseErrors.expectedImage)
            return
        }
        completion(image,nil)
    }
}
enum FirebaseErrors: Error {
    case expectedImage
}


func getDocumentsFromFirestore (firestoreReference: CollectionReference, completion: @escaping ([Cella])->()) {


    var cellaArray : [Cella] = []

    firestoreUsersReference.getDocuments { (querySnapshot, err) in
        if err != nil {
            print("There has been an error \(String(describing: err?.localizedDescription))")
        }
        else {

            for documents in querySnapshot!.documents {
                print("\(documents.documentID) => \(documents.data())")
                let data = documents.data()
                let title = data["userTitle"] as! String
                let description = data["userDescription"] as! String
                let imageURL = data["userImageURL"] as! String
                print("Title: \(String(describing: title)), Description: \(String(describing: description)), imageURL: \(imageURL)")

                self.cellCreationProcess(title: title, description: description, imageURL: imageURL, completion: { (newCell) in
                    cellaArray.append(newCell)
                })


                }
            completion(cellaArray)
            }

        }
    }

func cellCreationProcess(title: String, description: String, imageURL: String, completion: @escaping (Cella) -> ()) {

    let storagePath = Storage.storage().reference(forURL: imageURL)

    self.getImagesDownloaded(reference: storagePath, completion: { (image, error) in
        guard let image = image, error == nil else {
            print(String(describing : error?.localizedDescription))
            return
        }
        print("TITLE: \(String(describing: title)), IMAGE: \(image)")
        let newCell = Cella(image: image, title: title, bodyMessage: description)
        completion(newCell)
    })

}

}

ViewController where the TableView is:

class ViewControllerForTable: UIViewController, UITableViewDataSource, UITableViewDelegate {

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    let currentCell = tableView.cellForRow(at: indexPath) as! TableViewCell

    let information = Information(title: currentCell.title.text!, description: currentCell.bodyText.text!, sellerImage: currentCell.CellImage.image!)

    let destinationVC = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerDisplay") as! ViewControllerDisplay

    destinationVC.dataPassed = information

    self.navigationController?.pushViewController(destinationVC, animated: true)
}


@IBOutlet weak var UITableView: UITableView!


let image : UIImage = UIImage(named: "image1")!

var cells : [Cella] = []

override func viewDidLoad() {
    super.viewDidLoad()

    //states that this class is the delegate of the data and the object tableView within this VC
    UITableView.delegate = self
    UITableView.dataSource = self

    //force sets each of the the tableView rows to have a height of 200
    UITableView.rowHeight = 200

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

//Function hardcoded that creates the 5 cells to test the app

//MARK - Table view settings

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    //meaning my UITableView is going to have cells.count different rows
    return cells.count

}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    //Gets each item singularly in order from the dictionary of cells
    let cellFormat = cells[indexPath.row]

    //You need to do this Nib thing because you created a xib file

    let nib = UINib(nibName: String(describing: TableViewCell.self), bundle: nil)

    tableView.register(nib, forCellReuseIdentifier: "customCell")

    // Says that it is going to create a reusable cell with the identifier from the XIB file and it is going to use the class TableViewCell to access its properties
    let cellObject = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! TableViewCell

    //Creates and assigns the values from the item in the dictionary to the CellFormat for them to be displayed in the custom cell

    cellObject.setCell(cellFormatTaken: cellFormat)

    // returns the final Cell Item

    return cellObject

}

@IBAction func unwindToActivitieslList(sender: UIStoryboardSegue) {
    let sourceViewController = sender.source as? ViewController
    let activities = sourceViewController?.cellaObjects
    cells = activities!
    UITableView.reloadData()
}
}

------------------ UPDATE ------------------ I've deleted

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    super.prepare(for: segue, sender: sender)

    getDocumentsFromFirestore(firestoreReference: firestoreUsersReference) { (cellaArray) in
        self.cellaObjects = cellaArray
    }

}

And added the Button @IBAction func sardiniaNePressed(_ sender: UIButton) {

    getDocumentsFromFirestore(firestoreReference: firestoreUsersReference) { (cellaArray) in
        self.cellaObjects = cellaArray
        self.performSegue(withIdentifier:"sardiniaNe",sender:nil)
    }
}

I've also added the dispatch as kindly suggested. Still though I cannot get the data to be displayed. And another problem I have with the button is that whenever I click it, since the VCs are embedded in a navigation controller, they create a "duplicate" of the destination ViewController, and it keeps doing that for the whole time the app is open, leaving me with many duplicates, with empty rows.

1
From the description of the xcode tag: "USAGE NOTE: Use this tag only for questions about the Xcode IDE itself, and not for general Mac or iOS programming topics."Frank van Puffelen
Too much code here .. Basically you have to create operations and add dependency for image fetch operations .. Make a notification once all the operations is done. You need to redesign your present code since the completion handlers might return empty in many place..Digs

1 Answers

0
votes

You need to call this outside of prepare(for segue: ( directly in button action ) and in completion perform the segue

getDocumentsFromFirestore(firestoreReference: firestoreUsersReference) { (cellaArray) in
    self.cellaObjects = cellaArray
    self.performSegue(withIdentifier:"segue",sender:nil)
}

Also here you need DispatchGroup() as getting the image for each item is asynchronous

func getDocumentsFromFirestore (firestoreReference: CollectionReference, completion: @escaping ([Cella])->()) {


    var cellaArray : [Cella] = []

    firestoreUsersReference.getDocuments { (querySnapshot, err) in
        if err != nil {
            print("There has been an error \(String(describing: err?.localizedDescription))")
        }
        else {

            let g = DispatchGroup()

            for documents in querySnapshot!.documents {

                g.enter()
                print("\(documents.documentID) => \(documents.data())")
                let data = documents.data()
                let title = data["userTitle"] as! String
                let description = data["userDescription"] as! String
                let imageURL = data["userImageURL"] as! String
                print("Title: \(String(describing: title)), Description: \(String(describing: description)), imageURL: \(imageURL)")

                self.cellCreationProcess(title: title, description: description, imageURL: imageURL, completion: { (newCell) in
                    cellaArray.append(newCell)
                    g.leave()
                })


            }

            g.notify(queue: .main, execute: {
                 completion(cellaArray)
            })

        }

    }
}