在iOS中使用Swift 3实现CollectionView动态高度

10

最近,我正在尝试做一些练手项目。因此,我观看了斯坦福大学2017年CS193P课程“为iOS开发应用程序”。

现在,我正在做“Smashtag”项目,但我遇到了一些问题。我想使用CollectionView和两个UICollectionViewFlowLayout,在每个xib中显示两种类型(一种类似于tableView,另一种显示正方形的图像),通过SegmentedControl在CollectionView中显示。

我的问题如下:

  1. 如何使CollectionViewCell像TableView的UITableViewAutomaticDimension一样以动态高度显示?

    这是我刚刚尝试过的:

    我尝试使用UICollectionViewFlowLayoutAutomaticSize(@available(iOS 10.0, *))将CollectionView的autoResize设置成与TableView的autoDimension相同。

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
}

我的CollectionViewCell的Xib文件有问题:

图片描述

但是我运行应用程序时出现了错误。

我不知道我错过了什么或者我误解了什么。

图片描述
  1. 为什么这个解决方案会起作用?

    我在Stack Overflow上搜索了很多关于这个问题的解决方案,但我只找到了这个并且让我非常困惑:(

    这个解决方案给视图一个宽度约束(在CollectionViewCell xib中),并设置这个约束的常量值,它就能正常工作了! 我无法理解为什么宽度约束会使单元格的高度动态变化? 这真的让我感到困惑,我认为这很奇怪...

    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. 如何在不使用 UICollectionViewFlowLayoutAutomaticSize. ( @available(iOS 10.0, *) )的情况下实现CollectionViewCell的动态高度?

    因为我想要处理不仅在iOS 10中而且在iOS 10以下的不同iOS版本。

我真的想成为iOS开发专家和优秀的开发人员,但我还在学习和尝试。非常感谢任何回复。

谢谢!!

5个回答

16

在你的UICollectionViewLayout中提供estimatedSize

estimatedSize应该是xib尺寸检查器中显示的尺寸。

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

在您的UICollectionViewCell中,覆盖方法preferredLayoutAttributesFitting(_:)

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
}

您的集合视图单元现在将根据内容具有动态大小。


这是大部分繁重工作的代码,应优先选择。 - Rohan Bhale
你真棒!我认为这是最好的答案,也是最简单的。这是我的示例代码:https://gist.github.com/mabuak/d61446bdc6aba258dc29fd2f5d4dca81 - Ramadhan
太好了!谢谢 Atul - Chazman
完美地运行,谢谢。 - PinkeshGjr

8

使用以下代码根据显示的文本更改高度:

 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)          
 }

对不起,请问您的heights [indexPath.row] 是什么意思? - Kevin
但我没有看到你的“heights”声明在哪里? - Kevin
我认为更改单元格的框架应该写在 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 中,对吗? - Kevin
我很快会给您提供一些新的解决方案。 - Samarth Kejriwal
1
谢谢,我使用了您的逻辑,并尝试在sizeForItemAt函数中计算我的单元格高度,结果正如预期 :) - Kevin
显示剩余2条评论

5
我解决了它:)
我只是试图在sizeForItemAt函数中计算单元格的宽度和期望高度,这样它就有效了!
下面是代码:
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
}

2

您需要做的就是为UICollectionView布局创建一个IBOutlet, 将estimatedItemSize设置为任何大小, 在您的单元格类中,您必须指定单元格宽度(如果您只想使高度动态而宽度静态)和/或高度awakeFromNib并禁用translatesAutoresizingMaskIntoConstraintsself.contentView.translatesAutoresizingMaskIntoConstraints = false。 因此,最终结果应该是这样的

class ProfileViewController: UIViewController {
    @IBOutlet var collectionView: UICollectionView!
    @IBOutlet var collectionLayout: UICollectionViewFlowLayout!


    var person: Person!

override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.register(UINib(nibName: "ProfileCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
        collectionLayout.estimatedItemSize = CGSize(width: screenWidth * 0.4, height: 1)
        collectionView.reloadData()
    }

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

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

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! ProfileCollectionViewCell

        return cell
    }
    }

你的单元格类应该是这样的:
class ProfileCollectionViewCell: UICollectionViewCell {
@IBOutlet var containerWidthConstraint: NSLayoutConstraint!
 override func awakeFromNib() {
        super.awakeFromNib()

        self.contentView.translatesAutoresizingMaskIntoConstraints = false
        containerWidthConstraint.constant = screenWidth - (2 * 12)
    }
    }

这是一篇关于iOS自适应大小的好教程: https://engineering.shopspring.com/dynamic-cell-sizing-in-uicollectionview-fd95f614ef80


0
//Swift 5.0
//MARK:- You can use
func requiredWidthKK(text:String , cellHeight : CGFloat) -> CGFloat {
    let font = UIFont(name: Constant.fontName.poppins_medium, size: 16.0)
        let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: cellHeight))
        label.numberOfLines = 1
        //label.lineBreakMode = .byWordWrapping
        label.font = font
        label.text = text
        label.sizeToFit()
        //return label.frame.height
    return label.frame.width
}

//MAR: - Use
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let width = requiredWidth(text: arr[indexPath.row], cellHeight: 50)
    

返回 CGSize(width: width + 20, height:heightPerItem)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接