0

I am brand new to this so please go easy on me!

My problem is as follows:

I am trying to populate a scroll view (UICollectionView) with data from firebase.

I am positive that it is retrieving the data successfully as I can print the data using a for loop at the end of the firebase function.

The problem is that when I try to insert the data into the scroll view it says the data is out of range!!

It has been pointed out to me that a scrollview is populated with the scrollView delegate methods which I have displayed below.

It was also pointed out to me that my data source is 'self.pages'. How to I pass the data retrieved from firebase to the delegate methods?

Fatal error: Index out of range

Here is my code:

//arrays of names and descriptions which are populated in firebase() function
    var names:[String] = []
    var descriptions: [String] = []

The page class:

 let imageName: UIImage
 let headerText: String
 let bodyText: String 

Here is the viewDidLoad

override func viewDidLoad() {
        super.viewDidLoad()

        firebase()
        setupImages()
    }

Here is the image set up function:

 func setupImages(){

        self.pages = [
            Page(imageName: self.image, headerText: names[0], bodyText: descriptions[0]),

            Page(imageName: self.image, headerText: names[1], bodyText: descriptions[1]),

            Page(imageName: self.image, headerText: names[2], bodyText: descriptions[2]),

            Page(imageName: self.image, headerText: names[3], bodyText: descriptions[3]),

            Page(imageName: self.image, headerText: names[4], bodyText: descriptions[4]),
        ]

        self.collectionView?.backgroundColor = .white
        self.collectionView?.register(PageCell.self, forCellWithReuseIdentifier: "cellId")
        self.collectionView?.isPagingEnabled = true
    }

I think the issue is caused by the firebase data not being retrieved quick enough.

I say this because I have another function in a different class that seems to set it up:

override init(frame: CGRect) {
        super.init(frame: frame)
        //setup layout sets up constraints for the layout of the page
        setupLayout()
    }

Adding the UICollectionViewDataSource method's:

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

and:

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

        let page = pages[indexPath.item]
        cell.page = page
        return cell
    }

Firebase retrieval method:

func firebase()
    {
        //connection to firebase for the names and descriptions
        let db = Firestore.firestore()
        db.collection(restaurauntName).getDocuments { (snapshot, err) in

            if let err = err {
                print("Error getting documents: \(err)")
            } else {
                for document in snapshot!.documents {
                    let name = document.get("Name") as! String
                    let description = document.get("Description") as! String
                    //Add names and descriptions to the arrays
                    self.names.append(name)
                    self.descriptions.append(description)
                }
            }

            for x in self.names{
                print(x)
            }

        }
    }

Thank you for reading, any way someone can put me in the right direction would be appreciated!

  • would be good if you can share the tableView delegate methods and data source. – Eloy B. Jan 17 '20 at 01:46
  • @Kanongata I dont belive I have a tableView Delegate and what do you mean by data source? I have already shown how I get the data from firebase? –  Jan 17 '20 at 01:52
  • 1
    @mike he means the UICollectionViewDataSource method's – byaruhaf Jan 17 '20 at 02:05
  • @byaruhaf where will I find that? sorry if thats a stupid question? –  Jan 17 '20 at 02:07
  • The collectionView functions for `numberOfItemsInSection` and `cellForItemAt` – byaruhaf Jan 17 '20 at 02:09
  • @byaruhaf added that there, sorry I did not believe that code was relevant. thank you for taking time to look at this! –  Jan 17 '20 at 02:12
  • yes sorry, I meant the Collection View delegates of course. Thanks for clarifying @byaruhaf – Eloy B. Jan 17 '20 at 03:27
  • What does this have to do with Firebase? You're creating your dataSource in code with `self.pages = [`. Also you may want `pages[indexPath.row]` – Jay Jan 17 '20 at 19:17
  • @Jay thank you for your response! it has to do with Firebase because what is being added to the “out of range” array is data retrieved from firebase. Have you any idea why this is happening? Any help is appreciated! I have since made that adjustment and to no avail :( –  Jan 18 '20 at 05:35
  • You've not indicated what line is crashing so it's hard to know where the error is. An 'out of range' error would likely be attempting to access an array element outside of available indexes. There's nothing in the code that populates an array with *data from Firebase*. You've got one array `self.pages` that's populated within `func setupImages()`, and it's only accessed within the two delegate methods. Can you clarify where the error is occurring and *update the question* with that information? Oh. This `print(names[0])` would crash with that error if there was no index 0 – Jay Jan 18 '20 at 15:45
  • @Jay thank you for your comment! I have added the firebase method but as I have previously stated I do not believe that it has anything to do with the issue (as the function is working). I cannot figure out for the life of me why I cannot print out the results. Should I be looking at async functions or is there something I'm missing? –  Jan 22 '20 at 18:00
  • Ok. So I think you missed the point of my comment above. You have the question tagged as Firebase and there's nothing related to Firebase in your question. I see you've plugged in some Firebase code but what was added was *completely unrelated to the rest of the question*. That Firestore code populates two arrays that are not used anywhere. Your question states *I am trying to populate a scroll view (UICollectionView) with data from firebase* but you are not using the being retrieved from Firebase at all, other than printing it to console in the Firestore closure. – Jay Jan 22 '20 at 18:12
  • @Jay thank you for your correspondence. I will be populating the scroll view in the setupImages function. Where it says 'Header text' and 'body text' is where the contents of the array will be displayed. I was printing the 'names[0]' to test out where the issue was. As I said I am new to this so any help would be appreciated. –  Jan 22 '20 at 18:20
  • That's not how you populate a scrollView. The scroll view is populated with the scrollView delegate methods such as what you have `cellForItemAt`. That method will pull it's information from a dataSource. Your current dataSource is the `self.pages` array which contains no data from Firebase. You are populating that array within `setupImages`. Please update your question so we can determine what's being asked and also include your actual code. As is, you're not using Firebase for anything. – Jay Jan 22 '20 at 18:25
  • @Jay I think I understand, thank you for your help. question has been updated! please let me know if I need to add anything else –  Jan 22 '20 at 18:40

2 Answers2

1

Here's the issue, and your assumption is correct; the code is not flowing in the correct order.

override func viewDidLoad() {
   super.viewDidLoad()

   firebase()
   setupImages()
}

Firebase is asynchronous and your app needs to be structured in a way where you only attempt to work with Firebase data within the closure following the call. In your viewDidLoad function, you call firebase() to load the data but the setupImages function will actually execute before Firebase has had time to get the data from the server.

Fortunately, it's a simple fix. Here's the new functions with some updated names to make the flow more clear

override func viewDidLoad() {
   super.viewDidLoad()

   self.loadDataFromfirebase()
}

and then

func loadDataFromFirebase() {
   let db = Firestore.firestore()
   db.collection(restaurauntName).getDocuments { (snapshot, err) in
      if let err = err {
         print("Error getting documents: \(err)")
         return
      } else {
         for document in snapshot!.documents {
            let name = document.get("Name") as! String
            let description = document.get("Description") as! String
            self.names.append(name)
            self.descriptions.append(description)
         }
         self.setupImages() //safe to do this here as the firebase data is valid
         self.collectionView.reloadData()
      }
   }
}

the concern I have is if there 25 documents... your setupImages only accesses the first 5. Likewise, if there are 3 documents, the app will crash as that function would try to access objects at index 3, 4 and those won't exist.

Jay
  • 34,438
  • 18
  • 52
  • 81
  • Hi Jay, that works perfectly! thank you so much for sticking with me on this! It has been wrecking my head for days while I worked on other aspects of the project! I have no concerns about there being only 5 documents as there will be 5 for definite every time! thank you again! –  Jan 23 '20 at 23:58
  • although it is working screen goes black before loading the contents? is this again an asynchronous issue? –  Jan 24 '20 at 00:21
  • @mike Screen goes black? Can you elaborate a bit as if you're screen is going black that sounds like a totally separate issue with your view or something else. – Jay Jan 24 '20 at 13:29
  • Im sorry to be bothering you again! basically when I run the application, the screen goes black exactly like this video here : https://www.youtube.com/watch?v=xLOwMYdUKeg –  Jan 24 '20 at 15:46
  • I uploaded that there for you to see the problem first hand! –  Jan 24 '20 at 15:46
  • 1
    @mike That looks like a problem with how your view is being set up. I would put a breakpoint on this line `setupLayout` and see if it goes black before or after, then walk through the code from there line at a time to see what's causing it. – Jay Jan 24 '20 at 17:32
  • thanks again Jay for your help! Ive spent a few hours at it now and cant seem to find it. Any other ideas? It happens before setupLayout() is called and I can't seem to locate where the issue is. Ill keep at it and maybe will have to ask a new question. –  Jan 27 '20 at 14:44
  • Hi Jay, I was wondering if you could have a look at this question for me please! thanks! https://stackoverflow.com/questions/59938683/screen-goes-black-after-segue –  Feb 11 '20 at 18:03
-1

Looking at your code: You are using self.pages as your data source. I can't see a connection to Firebase in the code that you provided here.

The error could as well come from how you setup the cell when you pass the page in

cell.page = page

To quickly debug you can comment that line out and see if the error disappears.

Eloy B.
  • 565
  • 4
  • 13
  • thank you for your response! I have tried both of these solutions and nothing has come of them :( do you have any other ideas? –  Jan 17 '20 at 15:19
  • Why the downvote though? Anyway, you will need to provide more of your code. Somewhere you are trying to access an index out of range. It's not in the code that you provided here. – Eloy B. Jan 18 '20 at 00:13
  • That wasn’t me who down voted, I have upvoted you now. As I am a new contributor I didn’t think my votes counted. Any suggestions on what extra code I can add for context? Thank you again for your help –  Jan 18 '20 at 05:33
  • The OP is using a collectionView so indexPath.item is more generally used for those. indexPath.row is more of a tableView thing. – Jay Jan 18 '20 at 15:42
  • that's correct, it was just a troubleshooting idea. Both work – Eloy B. Jan 18 '20 at 23:26
  • @Kanongata have you another chance to look at this? –  Jan 23 '20 at 02:05