Search code examples
swiftxcodeuitableviewipaduicollectionview

How to adjust layout of UICollectionView which is embedded inside a UITableViewCell when ViewController is loaded?


In my iPad app, I have a UICollectionView with dynamic height embedded inside a UITableViewCell. My issue is when tableView is loaded, the collection view cells are not adjusted properly but as soon as I rotate or change screen orientation, the layout gets adjusted properly.

This is what I am getting at startup:

enter image description here

I am using collectionView's width to calculate width of these cells but I think when tableView is loaded, collectionView inside tableView cell has no proper layout. And this is correct layout after rotating app which I want at startup too:

enter image description here enter image description here

This is my main cellForRowAt method for tableView in main ViewController:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
        
        cell.layoutIfNeeded()
        
        // To invalidate layout when screen orientation changes
        if let layout = cell.collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            layout.invalidateLayout()
        }
        cell.heighCollectionView.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height
        
        return cell
    }

And im just reloading tableView when Screen Orientation changes:

 override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }

This is how im calculating layout of the CollectionView Cells:

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        if traitCollection.horizontalSizeClass == .compact {
            let width = self.collectionView.frame.size.width
            let height = width / 2
            return CGSize(width: width, height: height)
        }
        else {
            let width = (self.collectionView.frame.size.width - 10) / 2
            let height = width / 2
            return CGSize(width: width, height: height)
        }
    }

And in CustomTableViewCell subclass, inside awakeFromNib(), I am just setting delegate and datasource of collectionView:

 override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
        collectionView.delegate = self
        collectionView.dataSource = self
    }

My issue is only when View Controller is loaded, how do I set proper layout for collectionView when its first loaded?

In case you need to see full code, here is the link for the repo: https://github.com/shwaitkumar/RotateCollectionViewInsideTableViewCell.git


Solution

  • When init ViewController, it get defaults value of height which not the height you calculate. Just at viewDidAppear reload data of your tableView to get the calculate height.

    By the way, in func tableView(_ tableView:, cellForRowAt indexPath:) just for return cell so please remove calculate height there.

    Code will be like this

    class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
        @IBOutlet weak var tableView: UITableView!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            tableView.delegate = self
            tableView.dataSource = self
        }
        
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            self.tableView.reloadData()
        }
        
        override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
            super.viewWillTransition(to: size, with: coordinator)
            
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 1
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
            
            //cell.layoutIfNeeded() // removed
            // To invalidate layout when screen orientation changes
            if let layout = cell.collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
                layout.invalidateLayout()
            }
            
            //cell.heighCollectionView.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height // removed
            
            return cell
        }
    }