UICollectionView,全宽单元格,是否允许自动布局动态高度?


120

在垂直方向UICollectionView

是否可以使用全宽单元格,但是可以通过自动布局控制动态高度

这让我感到震惊,因为它可能是“ iOS中最重要的问题,没有一个很好的答案”。


重要:

请注意,在99%的情况下,要实现全宽单元格+自动布局动态高度,只需使用表格视图即可。就这么简单。


那么在哪里需要收集视图的示例是什么?

集合视图比表视图强大得多。

一个简单的示例,您实际上必须使用集合视图来实现全宽单元+自动布局动态高度:

如果在集合视图中的两个布局之间制作动画。例如,当设备旋转时,在1列和2列之间。

这是iOS中的一个普通习惯用法。不幸的是,这只能通过解决此质量保证中提出的问题来实现。:-/


UICollectionViewDelegateFlowLayout类可以实现
Sunil Prajapati

这是否意味着您实际上可以使用tableView?您需要什么其他功能来使这一过程变得过于复杂?
卡塔琳娜T.17年

1
嗨@CatalinaT。,集合视图在表视图上有很多很多附加功能。一个简单的例子是增加物理学。(如果您不明白我的意思,那是您在iOS中制作“有弹性”列表的方式。)
Fattie

您还可以应用在某种程度上物理(如SpringDampinginitialSpringVelocity)在表格单元格.....有使用的tableView看看这个名单蹦蹦,,,, pastebin.com/pFRQhkrX 你可以动画TableViewCell在表willDisplayCell:(UITableViewCell *)celldelgate方法@Fattie你能接受解决您的问题的答案,以帮助正在寻找相同问题的其他人
Dhiru

ohk,您是否使用列表视图尝试了Bouncy列表?@Fattie
Dhiru

Answers:


122

1.适用于iOS 13+的解决方案

在Swift 5.1和iOS 13中,您可以使用合成布局对象来解决您的问题。

以下完整的示例代码显示了如何UILabel在全角内显示多行UICollectionViewCell

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let items = [
        [
            "Lorem ipsum dolor sit amet.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
            "Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
        ]
    ]

    override func viewDidLoad() {
        super.viewDidLoad()

        let size = NSCollectionLayoutSize(
            widthDimension: NSCollectionLayoutDimension.fractionalWidth(1),
            heightDimension: NSCollectionLayoutDimension.estimated(44)
        )
        let item = NSCollectionLayoutItem(layoutSize: size)
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1)

        let section = NSCollectionLayoutSection(group: group)
        section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
        section.interGroupSpacing = 10

        let headerFooterSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .absolute(40)
        )
        let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
            layoutSize: headerFooterSize,
            elementKind: "SectionHeaderElementKind",
            alignment: .top
        )
        section.boundarySupplementaryItems = [sectionHeader]

        let layout = UICollectionViewCompositionalLayout(section: section)
        collectionView.collectionViewLayout = layout
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
        collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return items.count
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items[section].count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.label.text = items[indexPath.section][indexPath.row]
        return cell
    }

    override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
        headerView.label.text = "Header"
        return headerView
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate(alongsideTransition: { context in
            self.collectionView.collectionViewLayout.invalidateLayout()
        }, completion: nil)
    }

}

HeaderView.swift

import UIKit

class HeaderView: UICollectionReusableView {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .magenta

        addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

CustomCell.swift

import UIKit

class CustomCell: UICollectionViewCell {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        label.numberOfLines = 0
        backgroundColor = .orange
        contentView.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

预期显示:

在此处输入图片说明


2.适用于iOS 11+的解决方案

使用Swift 5.1和iOS 11,您可以子类化UICollectionViewFlowLayout并将其estimatedItemSize属性设置为UICollectionViewFlowLayout.automaticSize(这告诉系统您要处理autoresizing UICollectionViewCells)。然后,您必须重写layoutAttributesForElements(in:)layoutAttributesForItem(at:)以设置像元宽度。最后,您将不得不重写单元格的preferredLayoutAttributesFitting(_:)方法并计算其高度。

以下完整代码显示了如何UILabel在全角内显示多行UIcollectionViewCell(受UICollectionView的安全区域和UICollectionViewFlowLayout的限制):

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let items = [
        [
            "Lorem ipsum dolor sit amet.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
        ],
        [
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
            "Lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
        ]
    ]
    let customFlowLayout = CustomFlowLayout()

    override func viewDidLoad() {
        super.viewDidLoad()

        customFlowLayout.sectionInsetReference = .fromContentInset // .fromContentInset is default
        customFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
        customFlowLayout.minimumInteritemSpacing = 10
        customFlowLayout.minimumLineSpacing = 10
        customFlowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
        customFlowLayout.headerReferenceSize = CGSize(width: 0, height: 40)

        collectionView.collectionViewLayout = customFlowLayout
        collectionView.contentInsetAdjustmentBehavior = .always
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
        collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return items.count
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items[section].count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.label.text = items[indexPath.section][indexPath.row]
        return cell
    }

    override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
        headerView.label.text = "Header"
        return headerView
    }

}

CustomFlowLayout.swift

import UIKit

final class CustomFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
        layoutAttributesObjects?.forEach({ layoutAttributes in
            if layoutAttributes.representedElementCategory == .cell {
                if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
                    layoutAttributes.frame = newFrame
                }
            }
        })
        return layoutAttributesObjects
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let collectionView = collectionView else {
            fatalError()
        }
        guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
            return nil
        }

        layoutAttributes.frame.origin.x = sectionInset.left
        layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right
        return layoutAttributes
    }

}

HeaderView.swift

import UIKit

class HeaderView: UICollectionReusableView {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .magenta

        addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

CustomCell.swift

import UIKit

class CustomCell: UICollectionViewCell {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        label.numberOfLines = 0
        backgroundColor = .orange
        contentView.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
        layoutIfNeeded()
        layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
        return layoutAttributes
    }

}

以下是一些替代实现preferredLayoutAttributesFitting(_:)

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
    layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
    return layoutAttributes
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    label.preferredMaxLayoutWidth = layoutAttributes.frame.width
    layoutAttributes.frame.size.height = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
    return layoutAttributes
}

预期显示:

在此处输入图片说明


在添加节标题之前,这对我来说效果很好。节标题放错了位置,并覆盖了iOS 11中的其他单元。但是在iOS 12中可以正常工作。添加节标题是否遇到任何问题?
Nakul

非常感谢。你救了我的一周。♂‍♂️
Gaetan

1
每当大小更改时,CollectionView都会滚动到顶部
Sajith

1
好消息,@ ImanouPetit!我希望赏金更大!关于构图对象的精彩信息,谢谢。
Fattie

1
嗨,我正在尝试这个。即使像上面一样在viewdidload中设置布局,我也会崩溃,说明必须使用非nil布局参数初始化collectionview。有什么我想念的吗?这是适用于iOS 13的
Xcode11。– geistmate,

29

问题

您正在寻找自动高度,并且也希望具有完整的宽度,因此无法同时使用UICollectionViewFlowLayoutAutomaticSize

您想这样做UICollectionView,以下是您的解决方案。

步骤I:计算单元格的预期高度

1.如果只有UILabel in CollectionViewCell设置了numberOfLines=0,并且计算了的期望高度UIlable,则传递所有三个参数

func heightForLable(text:String, font:UIFont, width:CGFloat) -> CGFloat {
    // pass string, font, LableWidth  
    let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
     label.numberOfLines = 0
     label.lineBreakMode = NSLineBreakMode.byWordWrapping
     label.font = font
     label.text = text
     label.sizeToFit()

     return label.frame.height
}

2.如果CollectionViewCell仅包含UIImageView并且假设它的高度是动态的,则需要获取高度UIImage (您UIImageView必须具有AspectRatio约束)

// this will give you the height of your Image
let heightInPoints = image.size.height
let heightInPixels = heightInPoints * image.scale

3. 如果两者都包含,则计算它们的高度并将它们加在一起。

步骤II:返回CollectionViewCell

1.UICollectionViewDelegateFlowLayout在您的viewController中添加委托

2.实现委托方法

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

    // This is just for example, for the scenario Step-I -> 1 
    let yourWidthOfLable=self.view.size.width
    let font = UIFont(name: "Helvetica", size: 20.0)

    var expectedHeight = heightForLable(array[indePath.row], font: font, width:yourWidthOfLable)


    return CGSize(width: view.frame.width, height: expectedHeight)
}

希望对您有所帮助。


1
是否可能具有水平滚动的UICollectionView,而单元格的高度随着自动布局而垂直增加?
nr5

我想完成相同但一半的宽度和动态高度
Mihir Mehta,

return CGSize(width: view.frame.width/2, height: expectedHeight)还要考虑填充而不是返回宽度,否则两个单元格不能放入单行。
Dhiru

@ nr5您得到解决方案了吗?我的要求和你一样。谢谢。
Maulik Bhuptani

@MaulikBhuptani我无法使用“自动布局”完全做到这一点。必须创建一个自定义Layout类并重写一些类方法。
nr5

18

有几种方法可以解决此问题。

一种方法是,可以给集合视图流布局一个估计的大小并计算像元大小。

注意:如下面的评论中所述,从iOS 10开始,您不再需要提供和估计大小来触发对单元的调用 func preferredLayoutAttributesFitting(_ layoutAttributes:)。以前(iOS 9)如果要查询单元格prefferedLayoutAttributes,则要求您提供估计的大小。

(假设您正在使用情节提要板,并且收藏夹视图通过IB连接)

override func viewDidLoad() {
    super.viewDidLoad()
    let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
    layout?.estimatedItemSize = CGSize(width: 375, height: 200) // your average cell size
}

对于简单的单元格,通常就足够了。如果大小仍然不正确,则可以在“集合”视图单元格中覆盖func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes,这将使您对单元格大小进行更精细的控制。注意:您仍然需要给流布局一个估计的大小

然后覆盖func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes以返回正确的大小。

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
    let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
    let autoLayoutSize = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityDefaultLow)
    let autoLayoutFrame = CGRect(origin: autoLayoutAttributes.frame.origin, size: autoLayoutSize)
    autoLayoutAttributes.frame = autoLayoutFrame
    return autoLayoutAttributes
}

另外,您也可以使用大小调整单元格来计算中的单元格大小 UICollectionViewDelegateFlowLayout

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let width = collectionView.frame.width
    let size = CGSize(width: width, height: 0)
    // assuming your collection view cell is a nib
    // you may also instantiate a instance of our cell from a storyboard
    let sizingCell = UINib(nibName: "yourNibName", bundle: nil).instantiate(withOwner: nil, options: nil).first as! YourCollectionViewCell
    sizingCell.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    sizingCell.frame.size = size
    sizingCell.configure(with: object[indexPath.row]) // what ever method configures your cell
    return sizingCell.contentView.systemLayoutSizeFitting(size, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityDefaultLow)
}

尽管这些示例并非完美的生产就绪示例,但它们应该使您朝着正确的方向开始。我不能说这是最佳做法,但这对我有用,即使在包含多个标签的相当复杂的单元格中,也可能不包装成多行。


@Fattie不清楚'esitmatedItemSize'与动态调整大小的集合视图单元格有什么关系,因此,我想听听您的想法。(不是讽刺)
埃里克·穆菲

此外,如果此答案缺少某些有帮助的东西和/或您认为标准的东西,请告诉我。我不会争辩赏金,我只想对查看此问题的其他人有所帮助。
Eric Murphey

1
(包括或不包括估计的项目大小在当前的iOS中没有区别,在“全宽+动态高度”问题上,这两种方式都没有帮助。)最后,这段代码很少使用笔尖,并且它具有与动态高度的相关性很小(例如,一些动态高度的文本视图)。不过谢谢
Fattie

我也得到了这个解决方案,但是在我的情况下,iOS 11,Xcode 9.2,单元格内容似乎与单元格本身断开了连接-我无法获得将图像扩展到固定尺寸的整个高度或宽度的图像。我进一步注意到CV在中设置了约束preferredLayoutAttributesFitting。然后,我在上述函数中添加了自己的约束,将其绑定到estimateItemSize FWIW的固定维度,请参见下面的答案。
克里斯·康诺夫

16

我为该问题找到了一个非常简单的解决方案:在CollectionViewCell中,我得到了一个UIView(),实际上它只是一个背景。要获得全宽,我只需设置以下锚点

bgView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.size.width - 30).isActive = true // 30 is my added up left and right Inset
bgView.topAnchor.constraint(equalTo: topAnchor).isActive = true
bgView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
bgView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
bgView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true

“魔术”发生在第一行。我将widthAnchor动态设置为屏幕的宽度。同样重要的是减去CollectionView的插图。否则,该单元格将不会显示。如果您不想拥有这样的背景视图,只需使其不可见即可。

FlowLayout使用以下设置

layout.itemSize = UICollectionViewFlowLayoutAutomaticSize
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize

结果是一个具有动态高度的全尺寸单元格。

在此处输入图片说明


1
圣牛-太神奇了!如此明显!辉煌!
Fattie

1
只是TBC @ inf1783,您正在UICollectionView中执行此操作?(不是表格视图)-对吗?
Fattie

1
@Fattie是的,它是一个UICollectionView;)
inf1783

太棒了@ inf1783,迫不及待想尝试一下。顺便说一句,另一种好方法是对UIView进行子类化,并简单地添加一个固有宽度(我想它会产生相同的效果,并且可能也会使其在情节提要中起作用)
Fattie

1
每次尝试此操作时,都会遇到以下问题:UICollectionViewFlowLayout的行为未定义错误:/
Skodik.o

7

加工!!!在IOS:12.1 Swift 4.1上测试

我有一个非常简单的解决方案,无需打破约束即可使用。

在此处输入图片说明

我的ViewControllerClass

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    let cellId = "CustomCell"

    var source = ["nomu", "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ", "t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by", "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia,","nomu", "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ", "t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by", "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia,","nomu", "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. ", "t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by", "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia,"]

    override func viewDidLoad() {
        super.viewDidLoad()

        self.collectionView.delegate = self
        self.collectionView.dataSource = self
        self.collectionView.register(UINib.init(nibName: cellId, bundle: nil), forCellWithReuseIdentifier: cellId)

        if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
        }

    }

}


extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.source.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as? CustomCell else { return UICollectionViewCell() }
        cell.setData(data: source[indexPath.item])
        return cell
    }


}

CustomCell类:

class CustomCell: UICollectionViewCell {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var widthConstraint: NSLayoutConstraint!

    override func awakeFromNib() {
        super.awakeFromNib()
        self.widthConstraint.constant = UIScreen.main.bounds.width
    }

    func setData(data: String) {
        self.label.text = data
    }

    override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
        return contentView.systemLayoutSizeFitting(CGSize(width: self.bounds.size.width, height: 1))
    }

}

主要成分是systemLayoutSizeFitting中函数。而且我们还必须在约束内设置视图的宽度。


6

您必须将宽度约束添加到CollectionViewCell

class SelfSizingCell: UICollectionViewCell {

  override func awakeFromNib() {
      super.awakeFromNib()
      contentView.translatesAutoresizingMaskIntoConstraints = false
      contentView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
  }
}

终于有人在两行中做到了完美
Omar N Shamali

这会将单元格限制为固定的宽度,几乎无法自动调整大小。如果要在iPad上旋转设备或调整屏幕尺寸,它将保持固定。
Thomas Verbeek

4
  1. 设置estimatedItemSize您的流布局:

    collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
  2. 在单元格中定义一个宽度约束并将其设置为等于superview的宽度:

    class CollectionViewCell: UICollectionViewCell {
        private var widthConstraint: NSLayoutConstraint?
    
        ...
    
        override init(frame: CGRect) {
            ...
            // Create width constraint to set it later.
            widthConstraint = contentView.widthAnchor.constraint(equalToConstant: 0)
        }
    
        override func updateConstraints() {
            // Set width constraint to superview's width.
            widthConstraint?.constant = superview?.bounds.width ?? 0
            widthConstraint?.isActive = true
            super.updateConstraints()
        }
    
        ...
    }

完整的例子

在iOS 11上测试。


3

我个人发现,拥有UICollectionView的最佳方法是由AutoLayout确定大小,而每个Cell可以具有不同的大小,是实现UICollectionViewDelegateFlowLayout sizeForItemAtIndexPath函数,同时使用实际的Cell来测量大小。

我在一篇博客文章中谈到了这一点

希望这个可以帮助您实现您想要的。我不确定100%,但是我相信与UITableView不同,在UITableView中,您可以通过使用AutoLayout与

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44

UICollectionView没有让AutoLayout确定大小的方法,因为UICollectionViewCell不一定会填满整个屏幕宽度。

但这是一个问题:如果需要全屏宽度的单元格,为什么还要在自动调整大小单元格附带的旧版UITableView上使用UICollectionView呢?


拿着它,Aprit下面说UICollectionViewFlowLayoutAutomaticSize现在是在流式布局可在iOS10 ...
Fattie

我也看到了 显然,这就是iOS 10中的一件事。我刚刚看了有关WWDC的演讲:developer.apple.com/videos/play/wwdc2016/219但是,这将完全自动调整单元格的大小以适合其内容。我不确定如何告诉单元格(使用自动布局)以填充屏幕宽度,因为您无法在UICollectionViewCell及其父级之间设置约束(至少在StoryBoards中没有设置)
xxtesaxx

对。我们似乎还无法解决这个难以置信的明显问题的实际解决方案。:/
Fattie

根据讨论,您仍然可以覆盖sizeThatFits()或preferredLayoutAttributesFitting()并自己计算大小,但这并不是OP所要求的。无论如何,我仍然会对他/她何时需要全角UICollectionViewCell的用例感兴趣,以及为什么在这种特殊情况下不使用UITableView会是一件大事(确保可能存在某些情况,但是我猜您只需要自己进行一些计算即可)
xxtesaxx

考虑到这一点,您不能简单地使用集合视图来简单地设置“ columns == 1”(当设备侧身时,然后设置columns == 2),这是非常不可思议的。
Fattie

3

根据我对Eric答案的评论,我的解决方案与他的解决方案非常相似,但是我不得不在preferredSizeFor ...中添加一个约束,以约束到固定尺寸。

    override func systemLayoutSizeFitting(
        _ targetSize: CGSize, withHorizontalFittingPriority
        horizontalFittingPriority: UILayoutPriority,
        verticalFittingPriority: UILayoutPriority) -> CGSize {

        width.constant = targetSize.width

        let size = contentView.systemLayoutSizeFitting(
            CGSize(width: targetSize.width, height: 1),
            withHorizontalFittingPriority: .required,
            verticalFittingPriority: verticalFittingPriority)

        print("\(#function) \(#line) \(targetSize) -> \(size)")
        return size
    }

这个问题有很多重复项,我在这里详细回答了,并在此处提供了一个工作示例应用程序。


2

不知道这是否可以作为“真正的好答案”,但这就是我正在使用的解决方案。我的流程布局是水平的,并且我正在尝试使用自动布局来调整宽度,因此它与您的情况类似。

extension PhotoAlbumVC: UICollectionViewDelegateFlowLayout {
  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    // My height is static, but it could use the screen size if you wanted
    return CGSize(width: collectionView.frame.width - sectionInsets.left - sectionInsets.right, height: 60) 
  }
}

然后在修改了自动布局约束的视图控制器中,触发了NSNotification。

NotificationCenter.default.post(name: NSNotification.Name("constraintMoved"), object: self, userInfo: nil)

在我的UICollectionView子类中,我监听该通知:

// viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(handleConstraintNotification(notification:)), name: NSNotification.Name("constraintMoved"), object: nil)

并使布局无效:

func handleConstraintNotification(notification: Notification) {
    self.collectionView?.collectionViewLayout.invalidateLayout()
}

这将导致sizeForItemAt使用集合视图的新大小再次调用。在您的情况下,它应该能够根据布局中可用的新约束进行更新。


只是TBC标记,您是说实际上是在改变细胞的大小吗?(即,该单元格的大小为X,然后您的应用中发生了某些事情,大小为Y ..?)thx
Fattie

是。当用户在屏幕上拖动一个元素时,将触发一个事件,该事件将更新单元格的大小。
Mark Suman

1

在上viewDidLayoutSubviews,将设置estimatedItemSize为全宽(布局是指UICollectionViewFlowLayout对象):

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    return CGSize(width: collectionView.bounds.size.width, height: 120)
}

在单元格上,确保约束同时触及单元格的顶部和底部(以下代码使用制图法简化了约束的设置,但是您可以根据需要使用NSLayoutConstraint或IB来做到这一点):

constrain(self, nameLabel, valueLabel) { view, name, value in
        name.top == view.top + 10
        name.left == view.left
        name.bottom == view.bottom - 10
        value.right == view.right
        value.centerY == view.centerY
    }

瞧,您的细胞现在会自动增长!


0

没有一种解决方案对我有用,因为我需要动态宽度来适应iPhone的宽度。

    class CustomLayoutFlow: UICollectionViewFlowLayout {
        override init() {
            super.init()
            minimumInteritemSpacing = 1 ; minimumLineSpacing = 1 ; scrollDirection = .horizontal
        }

        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            minimumInteritemSpacing = 1 ; minimumLineSpacing = 1 ; scrollDirection = .horizontal
        }

        override var itemSize: CGSize {
            set { }
            get {
                let width = (self.collectionView?.frame.width)!
                let height = (self.collectionView?.frame.height)!
                return CGSize(width: width, height: height)
            }
        }
    }

    class TextCollectionViewCell: UICollectionViewCell {
        @IBOutlet weak var textView: UITextView!

        override func prepareForReuse() {
            super.prepareForReuse()
        }
    }




    class IntroViewController: UIViewController, UITextViewDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UINavigationControllerDelegate {
        @IBOutlet weak var collectionViewTopDistanceConstraint: NSLayoutConstraint!
        @IBOutlet weak var collectionViewTopDistanceConstraint: NSLayoutConstraint!
        @IBOutlet weak var collectionView: UICollectionView!
        var collectionViewLayout: CustomLayoutFlow!

        override func viewDidLoad() {
            super.viewDidLoad()

            self.collectionViewLayout = CustomLayoutFlow()
            self.collectionView.collectionViewLayout = self.collectionViewLayout
        }

        override func viewWillLayoutSubviews() {
            self.collectionViewTopDistanceConstraint.constant = UIScreen.main.bounds.height > 736 ? 94 : 70

            self.view.layoutIfNeeded()
        }
    }

0

从iOS 10开始,我们在流程布局上有了新的API。

您所要做的就是将您的flowLayout.estimatedItemSize设置为新常数UICollectionViewFlowLayoutAutomaticSize

资源


嗯,您确定可以做到*全宽吗?那么,基本上是一列?
Fattie

如果将UICollectionViewCell的宽度显式设置为全角,则可能会如此。
Arpit Dongre

不,它不会将宽度固定为全高,并且会忽略估计的尺寸值,除非您遵循上面的Eric的回答,或者下面/之后的挖掘。
克里斯·康诺夫

好的,不幸的是,这绝对与问题无关!嘿!:O
Fattie

0

通过2个简单步骤,可以使用AutoLayout自动调整CollectionView中的单元格大小:

  1. 启用动态单元大小调整

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. 拥有一个容器视图,并将containerView.widthAnchor.constraint从设置collectionView(:cellForItemAt:)为将contentView的宽度限制为collectionView的宽度。
class ViewController: UIViewController, UICollectionViewDataSource {
    ...

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MultiLineCell
        cell.textView.text = dummyTextMessages[indexPath.row]
        cell.maxWidth = collectionView.frame.width
        return cell
    }

    ...
}
class MultiLineCell: UICollectionViewCell{
    ....

    var maxWidth: CGFloat? {
        didSet {
            guard let maxWidth = maxWidth else {
                return
            }
            containerViewWidthAnchor.constant = maxWidth
            containerViewWidthAnchor.isActive = true
        }
    }

    ....
}

就是这样,您将获得理想的结果。请参阅以下要点以获取完整代码:

参考/学分:

屏幕截图: 在此处输入图片说明


-6

您必须在collectionViewController上继承类UICollectionViewDelegateFlowLayout。然后添加功能:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: view.frame.width, height: 100)
}

使用它,您可以得到屏幕宽度的宽度大小。

现在,您有了一个具有行的tableViewController的collectionViewController。

如果希望每个单元格的高度大小是动态的,则可能应该为所需的每个单元格创建自定义单元格。


7
这完全不是问题的答案:)这将给出一个固定的高度100,即自动布局甚至存在之前的编程高度。
Fattie
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.