Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CollectionView dynamic height with Swift 3 in iOS

Recently, I'm trying to do some projects for practicing. So I watched the course "Developing Apps for iOS", Stanford University, CS193P, 2017.

And now, I'm doing "Smashtag" project but I have some problems in it. I want to use a CollectionView with two UICollectionViewFlowLayout to show two types in each xib (one is like tableView, another is showing a image in square) of CollectionView by SegmentedControl.

My problems below :

  1. How to make CollectionViewCell showing in dynamic height just like TableView's UITableViewAutomaticDimension?

    And this is what I just tried below :

    I tried to make CollectionView's autoResize setting as like TableView's autoDimension with using UICollectionViewFlowLayoutAutomaticSize. ( @available(iOS 10.0, *) )

enum CollectionViewType {
  case tweet
  case image
}

// MARK: - Decide What Kind Of FlowLayout
func decideFlowLayout(type: CollectionViewType) -> UICollectionViewFlowLayout {

    // just for .image type
    var howManyImageShowing = 3  
    var imageShowingWidth: Double {
        return Double(self.view.frame.width) / Double(howManyImageShowing)
    }

    // .tweet is a type showing like TableViewCell -> this make me confused !!
    // another type is just showing a image in square -> I have no problem here
    let estimatedItemSize = type == .tweet ? CGSize(width: self.view.frame.width, height: 155.0) :
                                                CGSize(width: imageShowingWidth, height: imageShowingWidth)

    let collectionViewLayout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
    collectionViewLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

    // --------- Make this setting as TableView does --------- 
    // ------------ Somethis just like this below ------------
    /*
        tableView.estimatedRowHeight = 155.0
        tableView.rowHeight = UITableViewAutomaticDimension
    */

    collectionViewLayout.estimatedItemSize = estimatedItemSize
    collectionViewLayout.itemSize = UICollectionViewFlowLayoutAutomaticSize
    // ------------------------------------------------------- 

    collectionViewLayout.minimumLineSpacing = 0
    collectionViewLayout.minimumInteritemSpacing = 0

    return collectionViewLayout
}

Constraint of my CollectionViewCell xib here : enter image description here But it has bug when I run this app

I don't know what I miss or misunderstand. enter image description here

  1. Why this solution will work ?

    I already search so many solutions of this problem in Stack Overflow but I just find this and make me really confused :(

    This solution give the view a Width Constraint which is in CollectionViewCell xib and set this constraint's constant a value and it works! I can not configure out why Width Constraint will make the cell's height in dynamic? This make me really confused, I think this is weird...

    https://github.com/tttsunny/CollectionViewAutoSizingTest

class Cell: UICollectionViewCell {
    @IBOutlet weak var headerLabel: UILabel!
    @IBOutlet weak var descriptionLabel: UILabel!
    @IBOutlet weak var widthConstraint: NSLayoutConstraint!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
        self.contentView.translatesAutoresizingMaskIntoConstraints = false
        let screenWidth = UIScreen.main.bounds.size.width
        widthConstraint.constant = screenWidth - (2 * 12)
    }
}
  1. How to do CollectionViewCell dynamic height without using UICollectionViewFlowLayoutAutomaticSize. ( @available(iOS 10.0, *) ) ?

    Because I want to handle different iOS Versions not only in iOS 10 but also below iOS 10.

I really want to be pro in iOS development and a great developer but I'm still learning and trying. Any replies I will be greatly appreciated.

Thank you !!

like image 719
Kevin Avatar asked Jul 20 '17 02:07

Kevin


3 Answers

Provide estimatedSize to your UICollectionViewLayout.

The estimated size should be the size shown in the size inspector of your xib.

collectionViewLayout.estimatedItemSize = CGSize(width: collectionView.frame.width, height: 50)

Override the method preferredLayoutAttributesFitting(_:) in your UICollectionViewCell

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    setNeedsLayout()
    layoutIfNeeded()
        
    let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
        
    var frame = layoutAttributes.frame
    frame.size.height = ceil(size.height)
        
    layoutAttributes.frame = frame
        
    return layoutAttributes
}

Your collection view cell will now have dynamic size as per the content.

like image 167
Atul Pol Avatar answered Oct 07 '22 23:10

Atul Pol


Use the following code to change the height according to the text displayed:

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    return 0
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    return CGSizeMake(view.frame.width , 64)
}
override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 
{
  let approximateWidthOfContent = view.frame.width - x
    // x is the width of the logo in the left 

  let size = CGSize(width: approximateWidthOfContent, height: 1000)

  //1000 is the large arbitrary values which should be taken in case of very high amount of content 

 let attributes = [NSFontAttributeName: UIFont.systemFont(ofSize: 15)]            
 let estimatedFrame = NSString(string: user.bioText).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil)
 return CGSize(width: view.frame.width, height: estimatedFrame.height + 66)          
 }
like image 35
Samarth Kejriwal Avatar answered Oct 07 '22 22:10

Samarth Kejriwal


I solved it :)

I just tried to compute my cell's width and expect height in sizeForItemAt function and it works !

Code below :

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let imageShowingWidth: CGFloat = self.view.frame.width / CGFloat(self.howManyImageShowing)

    let labelName = "@\(self.tweetShowing[indexPath.row].user.screenName) (\(self.tweetShowing[indexPath.row].user.name))"
    let labelNameFont: UIFont = UIFont(name: "PingFangTC-Semibold", size: 16)!
    let labelNameWidth: CGFloat = self.view.frame.width - YourWidthOffSet// (YourWidthOffSet include all images' width and all margins)
    let labelNameHeight: CGFloat = self.getHeightForLable(labelWidth: labelNameWidth, labelText: labelName, labelFont: labelNameFont)

    let labelContentFont: UIFont = UIFont(name: "PingFangTC-Regular", size: 16)!
    let labelContentHeight: CGFloat = self.getHeightForLable(labelWidth: labelNameWidth, numberOfLines: 0, labelText: self.tweetShowing[indexPath.row].text, labelFont: labelContentFont)

    let cellHeight: CGFloat = labelNameHeight + labelContentHeight + YourHeightOffSet // (YourHeightOffSet means all margins)

    return self.typeControl.selectedSegmentIndex == 0 ? CGSize(width: self.view.frame.width, height: cellHeight) : CGSize(width: imageShowingWidth, height: imageShowingWidth)
}

func getHeightForLable(labelWidth: CGFloat, numberOfLines: Int = 1, labelText: String, labelFont: UIFont) -> CGFloat {
    let tempLabel: UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: labelWidth, height: CGFloat.greatestFiniteMagnitude))
    tempLabel.numberOfLines = numberOfLines
    tempLabel.text = labelText
    tempLabel.font = labelFont
    tempLabel.sizeToFit()
    return tempLabel.frame.height
}
like image 5
Kevin Avatar answered Oct 07 '22 21:10

Kevin