Learn2Code
Learn2Code

Reputation: 2280

Multi dimensional array populated from Firebase in Swift

I am attempting to populate a 2D array (UICollectionView) of Square objects:

class Square: NSObject {
     var sqrKey: String!
     var userId: String!

     init(SqrNumber: String, UserID: String) {
        self._sqrKey = SqrNumber
        self._userId = UserID        
     }

}

The ViewController is as such:

class CustomCollectionViewController: UICollectionViewController {

     var ref: DatabaseReference!

     var squares = [Square]()

     override func viewDidLoad() {

         super.viewDidLoad()

         self.ref = Database.database().reference(withPath: "PlayerBoxes")

         handle = ref.child("11062").observe(.value, with: { snapshot in
             var items: [Square] = []

             for item in snapshot.children {
                let square = Square(snapshot: item as! DataSnapshot)
                items.append(square)
             }
             self.squares = items
             self.collectionView?.reloadData()
        })
     }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 5 
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

        return 5
    }

    // Configure the cell
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CustomCollectionViewCell

        cell.square = squares[(indexPath as NSIndexPath).item]

        cell.label.text = cell.square?._sqrKey

        return cell
    }
}

I am running into two issues/error:

The first one is an issue more then an error which is that the Firebase read is not fully completed before the execution of the collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) function, which then causes the issue fatal error: Index out of range at the line: cell.square = squares[(indexPath as NSIndexPath).item]

How do I get around that!? How do I force the Firebase read to complete before going forward!?

The second part of my question is this, I have put dummy data to ensure the error/issue mentioned above does not get encountered (by hard coding an array of Square objects) This ensures that the fatal error is not encountered and I can test the rest of my code; but when I do so the output of the result when executing the cell.label.text = cell.square?._sqrKey I get the same values for each item in each of the sections of the UICollection!?

so the output is:

0,1,2,3,4
0,1,2,3,4
0,1,2,3,4
0,1,2,3,4
0,1,2,3,4

I am expecting the output to be

0,1,2,3,4,
5,6,7,8,9,
10,11,12,13,14
15,16,17,18,19
20,21,22,23,24

Can anyone help me understand how I can index the correct _sqrKey values!?

The hard coded Square object is the key value being incremented by 1 and thus the results above.

Upvotes: 0

Views: 499

Answers (1)

Alain T.
Alain T.

Reputation: 42133

The .item you get from the index path is relative to the section. Since your squares array is one dimensional, you are always accessing the first 5 elements.

you can compute the appropriate index by taking the section into account:

let index    = indexPath.section * 5 + indexPath.item
cell.square  = squares[index]

For the issues with the loading delay causing a runtime error, it will occur when your collection is displayed before the firebase request is completed (e.g. when setting its datasource). To handle that, you should respond to numberOfSections and numberOfItems using the actual size of the squares array rather than always returning 5

return squares.count/5   // for number of sections

return max(0,squares.count - section * 5)  // for items in section 

or, if the array is never partially filled:

return squares.count == 0 ? 0 : 5  // for number of sections

return squares.count == 0 ? 0 : 5  // for items in section 

[EDIT] if you want squares to be a 2D array, you'll have to declare it as an array of arrays, and adjust the loading and usage accordingly.

For example:

var squares:[[Square]] = []

...

var items: [[Square]] = []
var row:[Square]      = []
for item in snapshot.children 
{
  let square = Square(snapshot: item as! DataSnapshot)
  row.append(square)
  if row.count == 5
  {
    items.append(row)
    row = []
  }
}
self.squares = items

...

cell.square = squares[indexPath.section][indexPath.item]

...

return squares.count // for sections

...

return squares[section].count // for itms in section 

Upvotes: 2

Related Questions