UICollectionView gives "Index out of range" error

Viewed 1192

It is a question. Currently I would like to display UITableViewCell in UITableView for the number of users registered in the database. However, it becomes "Index out of range" in the UITableViewDataSOurce method (cellForItemAt). I understand the meaning of the error, but I do not know why such an error occurs. Could you tell me?

import UIKit
import Firebase
import FirebaseStorage
import FirebaseFirestore
import SDWebImage

struct MemberData {
  var image: URL?
  var text: String?
}

class MemberSelectViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
  let statusBsr = UIApplication.shared.statusBarFrame.height

  let db = Firestore.firestore()
  var teamIDFromFirebase: String = ""
  var fireAuthUID = (Auth.auth().currentUser?.uid ?? "no data")
  var memberData = [MemberData]()

  var memberImageArr = [Any]()
  var memberTextArr = [Any]()
  var memberUserIDArr = [Any]()

  override func viewDidLoad() {
      super.viewDidLoad()

      let collectionView = UICollectionView(frame: CGRect(x: 0, y: statusBsr, width: self.view.frame.width, height: self.view.frame.size.height - statusBsr), collectionViewLayout: UICollectionViewFlowLayout())

      let nibName = UINib(nibName: "memberCollectionViewCell", bundle: nil)
      collectionView.register(nibName, forCellWithReuseIdentifier: "memberCell")

      collectionView.delegate = self
      collectionView.dataSource = self

      self.view.addSubview(collectionView)
      getMemberData(collectionView: collectionView)
  }

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

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
      let size = self.view.frame.size.width / 4
      return CGSize(width: size, height: size)
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {

      let inset =  (self.view.frame.width / 4) / 5

      return UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset)
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
      return (self.view.frame.width / 4) / 5
  }

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
      let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "memberCell", for: indexPath) as! memberCollectionViewCell
          let cellData = memberData[indexPath.row]
          cell.memberImage.sd_setImage(with: cellData.image)
          cell.memberTitle.text = cellData.text
      return cell
  }
}


private extension MemberSelectViewController {
    private func getMemberData(collectionView: UICollectionView) {
    self.db.collection("users").document(self.fireAuthUID).addSnapshotListener { (snapshot3, error) in
        guard let document3 = snapshot3 else {
            print("erorr2 \(String(describing: error))")
            return
        }
        guard let data = document3.data() else { return }
        self.teamIDFromFirebase = data["teamID"] as? String ?? ""
        self.db.collection("users").whereField("teamID", isEqualTo: self.teamIDFromFirebase).getDocuments() { (querySnapshot, err) in
            if let err = err {
                print("Error getting documents: \(err)")
                return
            } else {
                var i = 0
                for document in querySnapshot!.documents {
                    guard var documentData: [String: Any] = document.data() else { return }
                    self.memberImageArr.append((documentData["image"] as? String)!)
                    self.memberTextArr.append((documentData["name"] as? String)!)
                    self.memberData.append(MemberData(image: URL(string: self.memberImageArr[i] as! String), text: self.memberTextArr[i] as! String))
                    i += 1
                }
            }
        }
    }
}
}
1 Answers

In this method

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

You inform the collection view that there will always be 24 rows, so when the view loads, it will start fetching content for 24 rows in the collection view.

However, your memberData array is defined like so:

var memberData = [MemberData]()

Meaning that initially it will be empty.

You then start adding content to memberData in getMemberData, but at that point, your collection view may have already started populating and is asking for content for row number 5 (for instance)...in an array with no elements, and that will crash.

So what you can do is:

  1. Change numberOfItemsInSection to not return a static value, but instead return the actual number of items in your memberData like so:

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

    once you're done loading data into memberData in the getMemberData function, you tell the collection view to reload and it will run through the entire process of building your collection view again, this time with data.

  2. in cellForItemAt you can make sure that you don not try to fetch content that isn't there:

    if indexPath.row < memberData.count {
        let cellData = memberData[indexPath.row
        //and so on
    }
    

Hope that give you some clues.

Related