Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making UITableView with embedded UICollectionView using UITableViewAutomaticDimension

I want to create an UITableView with Title label and embedded UICollectionView (which knows it's size) with some icons using UITableViewAutomaticDimension. The problem is UITableView has a problem with figuring out cell height when I have UICollectionView inside. I have to scroll an UITableView in order for it to recalculate sizes. But even then it has problems with height (it's too big if it was reused from bigger one). On top of that icons inside UICollectionView aren't known from the begginging, but they are intended to be loaded from the server.

I've also tried to create a height constraint for an UICollectionView, but this way I'm getting "Unable to simultaneously satisfy constraints" caused by conflict with UIView-Encapsulated-Layout-Height and my own constraint is being removed anyway.

I've created a GitHub repository with a sample project (I did it as simple as possible):

https://github.com/piotrros/CollectionViewInTableView

like image 517
Makalele Avatar asked Jan 04 '18 10:01

Makalele


4 Answers

As you are doing very much stuff in cellForRow so it need time to ready and so when you scroll it is not showing properly

Check following things.

ViewController.swift

In View Did load

Add tableView.rowHeight = UITableViewAutomaticDimension

and replace this method

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let foo = foos[indexPath.row]
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FooTableViewCell



    cell.titleLabel.text = foo.title
    cell.descriptionLabel.text = foo.description

    self.view.layoutIfNeeded()

    return cell
}

FooTableViewCell.swift

class FooTableViewCell: UITableViewCell {

    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var stackView: UIStackView!
    @IBOutlet weak var descriptionLabel: UILabel!
    @IBOutlet weak var iconsCollectionView: IconsCollectionView!
    @IBOutlet weak var const_Height_CollectionView: NSLayoutConstraint!


    override func awakeFromNib() {
        iconsCollectionView.translatesAutoresizingMaskIntoConstraints = false
        iconsCollectionView.initFlowLayout(superviewWidth: self.frame.width)
        iconsCollectionView.loadIconsSync()
        iconsCollectionView.setNeedsLayout()

    }
}

And I have removed StackView Form your storyboard and just give leading , trailing ,top and bottom constraints (Nothing complicated )

enter image description here

Here is output

enter image description here

Hope it is helpful to you

EDIT/UPDATE

YOu have many issues in your demo project. I have made many changes in your demo project.

Copy and paste XML in storyboard.

DON'T FORGOT TO CONNECT HEIGHT CONSTRAINT

Here is complete storyboard XML

 <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="CollectionViewInTableView" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="196" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="ZhZ-if-Yia">
                                <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                                <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                <prototypes>
                                    <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="cell" rowHeight="196" id="1WO-2S-MI9" customClass="FooTableViewCell" customModule="CollectionViewInTableView" customModuleProvider="target">
                                        <rect key="frame" x="0.0" y="28" width="375" height="196"/>
                                        <autoresizingMask key="autoresizingMask"/>
                                        <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="1WO-2S-MI9" id="2lF-aM-Z2U">
                                            <rect key="frame" x="0.0" y="0.0" width="375" height="195.5"/>
                                            <autoresizingMask key="autoresizingMask"/>
                                            <subviews>
                                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vs4-91-woo">
                                                    <rect key="frame" x="8" y="8" width="359" height="20.5"/>
                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                    <nil key="textColor"/>
                                                    <nil key="highlightedColor"/>
                                                </label>
                                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Description" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lD2-bR-lnm">
                                                    <rect key="frame" x="8" y="36.5" width="359" height="20.5"/>
                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                    <nil key="textColor"/>
                                                    <nil key="highlightedColor"/>
                                                </label>
                                                <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="6sQ-gf-Y6x" customClass="IconsCollectionView" customModule="CollectionViewInTableView" customModuleProvider="target">
                                                    <rect key="frame" x="8" y="65" width="359" height="92.5"/>
                                                    <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                                    <constraints>
                                                        <constraint firstAttribute="height" relation="greaterThanOrEqual" priority="750" constant="88" id="f9g-Du-pYg"/>
                                                    </constraints>
                                                    <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="Nts-Lf-FPD">
                                                        <size key="itemSize" width="50" height="50"/>
                                                        <size key="headerReferenceSize" width="0.0" height="0.0"/>
                                                        <size key="footerReferenceSize" width="0.0" height="0.0"/>
                                                        <inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
                                                    </collectionViewFlowLayout>
                                                    <cells>
                                                        <collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="item" id="8gX-Q1-0jG" customClass="BarCollectionViewCell" customModule="CollectionViewInTableView" customModuleProvider="target">
                                                            <rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
                                                            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                                            <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
                                                                <rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
                                                                <autoresizingMask key="autoresizingMask"/>
                                                                <subviews>
                                                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="6cf-uD-BQl">
                                                                        <rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
                                                                    </imageView>
                                                                </subviews>
                                                            </view>
                                                            <constraints>
                                                                <constraint firstAttribute="bottom" secondItem="6cf-uD-BQl" secondAttribute="bottom" id="0c3-ug-8tN"/>
                                                                <constraint firstItem="6cf-uD-BQl" firstAttribute="top" secondItem="8gX-Q1-0jG" secondAttribute="top" id="9xW-dN-c0m"/>
                                                                <constraint firstItem="6cf-uD-BQl" firstAttribute="leading" secondItem="8gX-Q1-0jG" secondAttribute="leading" id="dT6-RU-eE4"/>
                                                                <constraint firstAttribute="trailing" secondItem="6cf-uD-BQl" secondAttribute="trailing" id="nnz-oA-GgP"/>
                                                            </constraints>
                                                            <connections>
                                                                <outlet property="iconImageView" destination="6cf-uD-BQl" id="lFo-SS-Ego"/>
                                                            </connections>
                                                        </collectionViewCell>
                                                    </cells>
                                                </collectionView>
                                                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sXa-Mn-xxW">
                                                    <rect key="frame" x="8" y="165.5" width="359" height="30"/>
                                                    <state key="normal" title="Button"/>
                                                </button>
                                            </subviews>
                                            <constraints>
                                                <constraint firstItem="sXa-Mn-xxW" firstAttribute="leading" secondItem="2lF-aM-Z2U" secondAttribute="leading" constant="8" id="4ix-8u-0lO"/>
                                                <constraint firstAttribute="bottom" secondItem="sXa-Mn-xxW" secondAttribute="bottom" id="50m-Bv-FF8"/>
                                                <constraint firstAttribute="trailing" secondItem="sXa-Mn-xxW" secondAttribute="trailing" constant="8" id="8UW-vI-hge"/>
                                                <constraint firstItem="6sQ-gf-Y6x" firstAttribute="leading" secondItem="2lF-aM-Z2U" secondAttribute="leading" constant="8" id="9ht-Ez-lJX"/>
                                                <constraint firstAttribute="trailing" secondItem="vs4-91-woo" secondAttribute="trailing" constant="8" id="NOH-if-o7C"/>
                                                <constraint firstItem="lD2-bR-lnm" firstAttribute="leading" secondItem="2lF-aM-Z2U" secondAttribute="leading" constant="8" id="S2D-Kj-5Og"/>
                                                <constraint firstItem="vs4-91-woo" firstAttribute="leading" secondItem="2lF-aM-Z2U" secondAttribute="leading" constant="8" id="atk-7U-Mrw"/>
                                                <constraint firstAttribute="trailing" secondItem="6sQ-gf-Y6x" secondAttribute="trailing" constant="8" id="bfY-uh-Su2"/>
                                                <constraint firstItem="vs4-91-woo" firstAttribute="top" secondItem="2lF-aM-Z2U" secondAttribute="top" constant="8" id="gYO-XW-lmk"/>
                                                <constraint firstAttribute="trailing" secondItem="lD2-bR-lnm" secondAttribute="trailing" constant="8" id="pkH-Pf-xE1"/>
                                                <constraint firstItem="sXa-Mn-xxW" firstAttribute="top" secondItem="6sQ-gf-Y6x" secondAttribute="bottom" constant="8" id="w2O-4g-q6B"/>
                                                <constraint firstItem="6sQ-gf-Y6x" firstAttribute="top" secondItem="lD2-bR-lnm" secondAttribute="bottom" constant="8" id="xky-sw-IcM"/>
                                                <constraint firstItem="lD2-bR-lnm" firstAttribute="top" secondItem="vs4-91-woo" secondAttribute="bottom" constant="8" id="yG3-dE-CjF"/>
                                            </constraints>
                                        </tableViewCellContentView>
                                        <connections>
                                            <outlet property="const_Height_CollectionView" destination="f9g-Du-pYg" id="gw7-9T-hiU"/>
                                            <outlet property="descriptionLabel" destination="lD2-bR-lnm" id="M4K-k5-6LN"/>
                                            <outlet property="iconsCollectionView" destination="6sQ-gf-Y6x" id="FO2-dP-VNH"/>
                                            <outlet property="titleLabel" destination="vs4-91-woo" id="HHy-1V-bTW"/>
                                        </connections>
                                    </tableViewCell>
                                </prototypes>
                            </tableView>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="ZhZ-if-Yia" firstAttribute="top" secondItem="8bC-Xf-vdC" secondAttribute="top" id="J8c-wQ-FxB"/>
                            <constraint firstItem="ZhZ-if-Yia" firstAttribute="bottom" secondItem="6Tk-OE-BBY" secondAttribute="bottom" id="KCX-nj-zXy"/>
                            <constraint firstItem="ZhZ-if-Yia" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="QMU-w2-uUY"/>
                            <constraint firstItem="ZhZ-if-Yia" firstAttribute="trailing" secondItem="6Tk-OE-BBY" secondAttribute="trailing" id="yx7-yg-aqC"/>
                        </constraints>
                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                    </view>
                    <connections>
                        <outlet property="tableView" destination="ZhZ-if-Yia" id="WdR-nu-gjc"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="117.59999999999999" y="118.29085457271366"/>
        </scene>
    </scenes>
</document>

FOOTableviewCell.swift

protocol TableViewDelegate {
    func cellTapped (for:FooTableViewCell)
}


class FooTableViewCell: UITableViewCell {


    static let singleCellHeight = 88;

    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var descriptionLabel: UILabel!
    @IBOutlet weak var iconsCollectionView: IconsCollectionView!
    @IBOutlet weak var const_Height_CollectionView: NSLayoutConstraint!

    var delegateCollection : TableViewDelegate?
    var bars:[Bar] = [] {
        didSet {
            self.iconsCollectionView.reloadData()
            iconsCollectionView.setNeedsLayout()
            self.layoutIfNeeded()
            const_Height_CollectionView.constant =  iconsCollectionView.contentSize.height
            self.layoutIfNeeded()
        }
    }

    override func awakeFromNib() {
        iconsCollectionView.translatesAutoresizingMaskIntoConstraints = false
        iconsCollectionView.initFlowLayout(superviewWidth: self.frame.width)
        iconsCollectionView.setNeedsLayout()
        iconsCollectionView.dataSource = self
        iconsCollectionView.delegate = self
        const_Height_CollectionView.constant =  iconsCollectionView.contentSize.height
        self.layoutIfNeeded()
        self.setNeedsLayout()
    }

    func cellTapped () {
        iconsCollectionView.setNeedsLayout()
        self.layoutIfNeeded()
        self.setNeedsLayout()

        const_Height_CollectionView.constant =  iconsCollectionView.contentSize.height

        self.delegateCollection?.cellTapped(for: self)

    }

}

extension FooTableViewCell : UICollectionViewDataSource {

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

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "item", for: indexPath) as! BarCollectionViewCell

        let row = indexPath.row
        if(row >= bars.count) {
            cell.iconImageView.image = UIImage(named: "add.png")
            return cell
        } else {
            let bar = bars[row]
            cell.iconImageView.image = UIImage(named: bar.imageName)
            print(bar.imageName)

            return cell
        }

    }

}

extension FooTableViewCell : UICollectionViewDelegate {

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        //deselectItem(at: indexPath, animated: true)
        if(indexPath.row >= bars.count) { //it's a plus button
            self.delegateCollection?.cellTapped(for: self)
        }
    }
}

IconCollectionView.swift

import UIKit



class IconsCollectionView: DynamicCollectionView {


    var columnLayout:ColumnFlowLayout?



    override func awakeFromNib() {
    }

    func initFlowLayout(superviewWidth:CGFloat) {
        let layout = ColumnFlowLayout(
            cellsPerRow: 4,
            superviewWidth: superviewWidth,
            minimumInteritemSpacing: 0,
            minimumLineSpacing: 0,
            sectionInset: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        )
        columnLayout = layout
        collectionViewLayout = layout
    }
}

ViewController.Swift

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    var foos:[Foo] = []

    var bars:[[Bar]] = [[]]

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.dataSource = self
        tableView.delegate = self
        tableView.estimatedRowHeight = 188
        tableView.rowHeight = UITableViewAutomaticDimension

        for i in stride(from: 1, to: 10, by: 1) {
            let foo = Foo()
            foo.title = "Item \(i)"
            foo.description = "Description \(i)"
            foos.append(foo)
        }
        bars.removeAll()
        for _ in 0 ..< foos.count {
            bars.append(self.loadIconsSync())
        }
    }

    func loadIconsSync() -> [Bar] {

        var barObjects :[Bar] = []

        let iconsCount = Utils.rnd(3, 8)
        for _ in stride(from: 1, to: iconsCount, by: 1) {
            barObjects.append(self.getRandomItem())
        }

        return barObjects
    }

    func getRandomItem() -> Bar {
        let randomIndex = Utils.rnd(1, 10)
        let bar = Bar()
        bar.imageName = "icon_\(randomIndex).png"
        return bar
    }

}

extension ViewController : UITableViewDataSource,UITableViewDelegate {

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let foo = foos[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FooTableViewCell

        var bar = bars[indexPath.row]
        cell.bars = bar

        cell.titleLabel.text = foo.title
        cell.descriptionLabel.text = foo.description
        cell.delegateCollection = self
        self.view.layoutIfNeeded()
        cell.const_Height_CollectionView.constant =  cell.iconsCollectionView.contentSize.height
        self.view.layoutIfNeeded()

        return cell
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return foos.count
    }

}

extension ViewController : TableViewDelegate {
    func cellTapped(for obj: FooTableViewCell) {
        if let indexPath = tableView.indexPath(for: obj) {
            bars[indexPath.row].append(getRandomItem())
            self.tableView.beginUpdates()
            self.tableView.reloadRows(at: [indexPath], with: .automatic)
            self.tableView.endUpdates()
        }
    }

}

OUTPUT

enter image description here

EDIT / UPDATE 2

I was not aware about orientation supports and ipad support.

now when the orientation changes we have to re-layout the collection view.

So logic is

total items + 1 (+ 1 because of that plus icon )

Item Size * (total Items / 3).rounded

suppose you have 7 Items

so item size is 93 (Per row) * ( 8 / 3).rounded = 279

So here you need to manage some hardcoded values as per your requirement for iPad and Landscape mode. For now I am considering 3 objects per row same as iPhone design.

Here Hardcoded cell number is 3 You can manage your own.

Step1:

Add Following method in viewContorller.swift

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {

        super.viewWillTransition(to: size, with: coordinator)
            self.tableView.beginUpdates()
            self.tableView.reloadData()
            self.tableView.endUpdates()
        }

Replace cellForRowAtIndexPath

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let foo = foos[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FooTableViewCell

        cell.iconsCollectionView.initFlowLayout(superviewWidth: self.tableView.frame.width)

        let bar = bars[indexPath.row]
        cell.bars = bar

        cell.titleLabel.text = foo.title
        cell.descriptionLabel.text = foo.description
        cell.delegateCollection = self
        self.view.layoutIfNeeded()

        let items:CGFloat = CGFloat(bar.count + 1)
        let value = (items / 3.0).rounded(.awayFromZero)
        cell.const_Height_CollectionView.constant =  CGFloat((cell.iconsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout).itemSize.height * value)

        self.view.layoutIfNeeded()
        cell.iconsCollectionView.setNeedsLayout()
        return cell
    }

And

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if let  footCell =  cell as?  FooTableViewCell {
            footCell.const_Height_CollectionView.constant = footCell.iconsCollectionView.contentSize.height
            self.view.layoutIfNeeded()

        }
    }

In TableviewCell

var bars:[Bar] = [] {
        didSet {
            self.iconsCollectionView.reloadData()
            iconsCollectionView.setNeedsLayout()
            self.layoutIfNeeded()

            let items:CGFloat = CGFloat(bars.count + 1)
            let value = (items / 3.0).rounded(.awayFromZero)

            const_Height_CollectionView.constant =  CGFloat((iconsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout).itemSize.height * value)

            self.layoutIfNeeded()
        }
    }

and

func cellTapped () {
        iconsCollectionView.setNeedsLayout()
        self.layoutIfNeeded()
        self.setNeedsLayout()
        let items:CGFloat = CGFloat(bars.count + 1)
        let value = (items / 3.0).rounded(.awayFromZero)
        const_Height_CollectionView.constant =  CGFloat((iconsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout).itemSize.height * value)
        self.delegateCollection?.cellTapped(for: self)

    }
like image 186
Prashant Tukadiya Avatar answered Nov 15 '22 20:11

Prashant Tukadiya


I think you need to implement estimatedHeightForRowAt, so instead of below method

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

Use below method:

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

This should work, try it!

like image 23
torap Avatar answered Nov 15 '22 18:11

torap


UICollectionView itself does not have an intrinsic content size, thus you have to create an explicit height constraint for it, as you say you did.

If the layout seems to be working then, but you are getting unsatisfiable constraints with UIView-Encapsulated-Layout-Height, then read the following.

That's a result of how UITableViewAutomaticDimension works - it sets height of the cell based on some default value (I still don't know how does it get that value), then uses autolayout to calculate the height your cell wants, and then updates the UIView-Encapsulated-Layout-Height to fit the new height. But in the process your constraints and the UIView-Encapsulated-Layout-Height constraint gets into conflict. The solution is to set the priority of one of your constraints to collectionViewHeightConstraint.priority = UILayoutPriority(rawValue: 999). This way the constraints won't get broken, and autolayout will work without warnings + note that in the end UIView-Encapsulated-Layout-Height will get updated to the proper height, so lowering priority will not prevent layout to work. See my answer to another similar question for reference.

EDIT

I forked your project, fixed it, and created a pull request (see github).

The main problem I believe was a very trivial bug. You used bars.count to calculate the intrinsic size of the collection, but in the end you had bars.count + 1 items in collection (the + icon). Therefore if + icon had to be put on a new row alone, it seemed that your layout wasn't working.

So just change

let rows = ceil(Double(bars.count) / Double(columnLayout.cellsPerRow))

to

let rows = ceil(Double(bars.count + 1) / Double(columnLayout.cellsPerRow))

in intrinsicContentSize of the IconsCollectionView.

There were other things I would change, and I changed it in the project - in your cell prototype in storyboards.

First, I removed that explicit constraint on height of the collection set to 88 points. You have intrinsic size, so you don't need that (if storyboards complain, don't mind them, they do not know that you implemented intrinsic size.

Second, the button at the bottom was not constrained to the bottom of the cell. Thus the cell height calculation could not work (you want the cell to size itself to fit its contents, so you need to constrain all the sides of the cell to its contents). But that maybe was a result of someone else's advices, because I think older commits had it working.

Third, a minor note, is to the way you used superviewWidth to calculate the size. I changed it to selfView, and changed the way its value was calculated. Because in the end the width of collection view is not equal to cell's width, but to cell's width -16. That's because collectionView was constrained to start 8 points from the left of the cell's contentView, and 8 points from the right of the cell's contentView. Although that was not the main issue, it might cause some problems later.

Finally, I left you a comment in you async loading of the items. If you load it asynchronously, the cell will most probably get presented before you get your data back, and you will want to refresh the tableView to adapt to newly loaded data. Conceptually, that's the same as dynamically expanding and collapsing cells - for that I will refer you to my answer to that question.

like image 21
Milan Nosáľ Avatar answered Nov 15 '22 18:11

Milan Nosáľ


I downloaded the git hub source code an fixed the issue. Your code is perfect for setting the cell size , the problem lies you column flow layout. You were setting to show 4 cells in a row, but according to your source code only 3 cells were displayed.I digged deeper into it and found out that because of this the number of cells are calculated wrong in your "intrinsicContentSize" in Icons collection view. The width of super view you are passing in the flow layout function is causing problem. i guess there was some spacing issue for the width. So here is how my new code looks :

override var itemSize: CGSize {
        get {
            let itemWidth = ((superviewWidth - 50) / CGFloat(cellsPerRow)).rounded(.down)
            return CGSize(width: itemWidth, height: itemWidth)
        }
        set {
            super.itemSize = newValue
        }
    }

override var intrinsicContentSize: CGSize {
        guard let columnLayout = columnLayout else { return CGSize(width: 0, height: 0) }
        let itemSize = columnLayout.itemSize
        let rows = ceil(Double(bars.count) / Double(columnLayout.cellsPerRow))
        let w = columnLayout.superviewWidth
        let h = itemSize.height * CGFloat(rows)


        print("itemSize: \(itemSize.width), \(itemSize.height), intrinsicContentSize: \(w), \(h); rows = \(rows)")

        return CGSize(width: w, height: h)
    }

I have changed only these two function and your code worked perfectly. enter image description here

If you want source code I can push it on git too. Hope this helps you out!

like image 36
Pallavi Srikhakollu Avatar answered Nov 15 '22 20:11

Pallavi Srikhakollu