Search code examples
swiftxcodeuitableviewuicollectionviewreloaddata

UICollectionView numberOfItemsInSection not being called on reloadData() - Swift Xcode


I have two view controllers – SecondTestViewController and SecondContentViewController. SecondContentViewController is a floating panel inside SecondTestViewController. SecondTestViewController has a UITableView and SecondContentViewController has a UICollectionView.

SecondContentViewController:

override func viewDidLoad() {
        super.viewDidLoad()
//        let layout = UICollectionViewFlowLayout()
//        layout.scrollDirection = .vertical
        myCollectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout())
        guard let myCollectionView = myCollectionView else {
            return
        }
        myCollectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
        myCollectionView.delegate = self
        myCollectionView.dataSource = self
        myCollectionView.backgroundColor = .white
        myCollectionView.translatesAutoresizingMaskIntoConstraints = false
        myCollectionView.allowsSelection = true
        myCollectionView.isSpringLoaded = true
        view.backgroundColor = .white
        view.addSubview(myCollectionView)
        NSLayoutConstraint.activate([
            myCollectionView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
            myCollectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
            myCollectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
            myCollectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
        ])
        
    }
    
    private func collectionViewLayout() -> UICollectionViewLayout {
        let layout = UICollectionViewFlowLayout()
        let cellWidthHeightConstant: CGFloat = UIScreen.main.bounds.width * 0.2
        layout.sectionInset = UIEdgeInsets(top: 50,
                                           left: 10,
                                           bottom: 0,
                                           right: 10)
        layout.scrollDirection = .vertical
        layout.minimumInteritemSpacing = 0
        layout.itemSize = CGSize(width: cellWidthHeightConstant, height: cellWidthHeightConstant)
        
        return layout
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//        return contentData.count
        print("numberOfItemsInSection activated")
        print("numberOfItemsInSection = \(reviewDataController?.tableViewReviewData.count ?? 100)")
        return reviewDataController?.tableViewReviewData.count ?? 0
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        print("cellForItemAt activated")
        let cell = myCollectionView?.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
//        cell.data = self.contentData[indexPath.item]
        cell.data = self.reviewDataController.tableViewReviewData[indexPath.item]
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: collectionView.frame.width / 5, height: collectionView.frame.height / 10)
    }
    
    
    func updateTheCollectionView() {
        print("updateCollectionView activated")
        print("reviewData count = \(reviewDataController.tableViewReviewData.count)")
        myCollectionView?.reloadData()
    }

When a cell in SecondTestViewController's tableView is tapped, it takes a photo, passes the data, and then calls SecondContentViewController's updateTheCollectionView() function.

This function is called in SecondTestViewController after a photo has been taken and various other tasks completed (like updating the variable, etc.).

func passDataToContentVC() {
        let contentVC = SecondContentViewController()
        contentVC.reviewDataController = self.reviewDataController
        contentVC.updateTheCollectionView()
    }

As you can see, reloadData is supposed to be called inside this function. It should now update the view with the new photo. For some reason, it's like reloadData is never called because neither numberOfItemsInSection nor cellForItemsAt are being activated a second time.

What is going wrong? The print statements show that the data is being passed (they show an updated quantity after a photo has been taken). The view simply is not updating with the new data because the two collectionView functions are not being called.

Edit: Here is the floating panel code.

fpc = FloatingPanelController()
        fpc.delegate = self
        guard let contentVC = storyboard?.instantiateViewController(identifier: "fpc_secondContent") as? SecondContentViewController else {
            return
        }
        
        fpc.isRemovalInteractionEnabled = false
        fpc.set(contentViewController: contentVC)
        fpc.addPanel(toParent: self)

It's essentially inside of viewDidLoad for the SecondTestViewController.

For more information on how it works: Visit this github

As far as the didSelectRowAt function goes, it's too much code to show here. reviewDataController is dependency injection made up of an array of a struct. Essentially all that happens is when a cell is tapped, it pulls up the camera and allows the user to take a photo, then it stores the photo in the array inside of reviewDataController along with some other properties in the struct that the array is made of. The important part is that it's supposed to pass this data to the floating panel which then updates the collectionView showing the photo that was just taken.


Solution

  • Because you are passing data from SecondTestViewController to SecondContentViewController, which is forwards and not backwards, you don't need delegates or anything.

    Instead, all you need to do is retain a reference to SecondContentViewController. Try something like this:

    class SecondTestViewController {
        var secondContentReference: SecondContentViewController? /// keep a reference here
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let fpc = FloatingPanelController()
            fpc.delegate = self
            guard let contentVC = storyboard?.instantiateViewController(identifier: "fpc_secondContent") as? SecondContentViewController else {
                return
            }
            
            fpc.isRemovalInteractionEnabled = false
            fpc.set(contentViewController: contentVC)
            fpc.addPanel(toParent: self)
    
            self.secondContentReference = contentVC /// assign contentVC to the reference
        }
    }
    

    secondContentReference keeps a reference to your SecondContentViewController. Now, inside passDataToContentVC, use that instead of making a new instance with let contentVC = SecondContentViewController().

    Replace

    func passDataToContentVC() {
        let contentVC = SecondContentViewController()
        contentVC.reviewDataController = self.reviewDataController
        contentVC.updateTheCollectionView()
    }
    

    ... with

    func passDataToContentVC() {
        secondContentReference?.reviewDataController = self.reviewDataController
        secondContentReference?.updateTheCollectionView()
    }