UICollectionView,全宽单元格,允许自动布局动态高度? [英] UICollectionView, full width cells, allow autolayout dynamic height?

查看:15
本文介绍了UICollectionView,全宽单元格,允许自动布局动态高度?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意 2021 年!请参阅@Ely 关于 UICollectionLayoutListConfiguration 的回答 !!!!


在一个垂直的 UICollectionView ,

是否可以有全宽单元格,但允许动态高度autolayout控制?

这可能是iOS 中最重要的问题,但没有真正好的答案".


重要:

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


那么你需要一个集合视图的例子是什么?

集合视图比表格视图强大得多.

一个简单的示例,您必须使用具有自动布局动态高度的集合视图:

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

这是 iOS 中的常见习语.不幸的是,它只能通过解决此 QA 中提出的问题来实现.:-/

解决方案

1.iOS 13+ 解决方案

在 Swift 5.1 和 iOS 13 中,您可以使用

<小时>

2.iOS 11+ 解决方案

使用 Swift 5.1 和 iOS 11,您可以继承 UICollectionViewFlowLayout 并将其 estimatedItemSize 属性设置为 UICollectionViewFlowLayout.automaticSize(这会告诉系统你想处理自动调整 UICollectionViewCells).然后,您必须覆盖 layoutAttributesForElements(in:)layoutAttributesForItem(at:) 以设置单元格宽度.最后,您必须重写单元格的 preferredLayoutAttributesFitting(_:) 方法并计算其高度.

下面的完整代码展示了如何在全宽UIcollectionViewCell内显示多行UICollectionViewCell(受UICollectionView的安全区域和UICollectionViewFlowLayout 的插图):

CollectionViewController.swift

导入 UIKit类 CollectionViewController: UICollectionViewController {让项目= [["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 incidudunt.","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 incidudunt.","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.",]]让 customFlowLayout = CustomFlowLayout()覆盖 func viewDidLoad() {super.viewDidLoad()customFlowLayout.sectionInsetReference = .fromContentInset//.fromContentInset 是默认值customFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSizecustomFlowLayout.minimumInteritemSpacing = 10customFlowLayout.minimumLineSpacing = 10customFlowLayout.sectionInset = UIEdgeInsets(上:10,左:10,下:10,右:10)customFlowLayout.headerReferenceSize = CGSize(宽度:0,高度:40)collectionView.collectionViewLayout = customFlowLayoutcollectionView.contentInsetAdjustmentBehavior = .alwayscollectionView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")}覆盖func numberOfSections(在collectionView中:UICollectionView)->诠释{返回 items.count}覆盖 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) ->诠释{返回项目[section].count}覆盖 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) ->UICollectionViewCell {让 cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as!自定义单元cell.label.text = items[indexPath.section][indexPath.row]返回单元格}覆盖 func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) ->UICollectionReusableView {让 headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath) as!页眉视图headerView.label.text = "标题"返回标题视图}}

CustomFlowLayout.swift

导入 UIKit最终类 CustomFlowLayout: UICollectionViewFlowLayout {覆盖 func layoutAttributesForElements(in rect: CGRect) ->[UICollectionViewLayoutAttributes]?{让 layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as?[UICollectionViewLayoutAttributes]layoutAttributesObjects?.forEach({ layoutAttributes in如果 layoutAttributes.representedElementCategory == .cell {如果让 newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {layoutAttributes.frame = newFrame}}})返回布局属性对象}覆盖 func layoutAttributesForItem(at indexPath: IndexPath) ->UICollectionViewLayoutAttributes?{守卫让collectionView = collectionView else {致命错误()}守卫让 layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as?UICollectionViewLayoutAttributes 其他 {返回零}layoutAttributes.frame.origin.x = sectionInset.leftlayoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right返回布局属性}}

HeaderView.swift

导入 UIKit类 HeaderView: UICollectionReusableView {让标签 = UILabel()覆盖初始化(帧:CGRect){超级初始化(帧:帧)背景颜色 = .洋红色添加子视图(标签)label.translatesAutoresizingMaskIntoConstraints = falselabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = truelabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true}需要初始化?(编码器 aDecoder:NSCoder){fatalError("init(coder:) 尚未实现")}}

CustomCell.swift

导入 UIKit类 CustomCell:UICollectionViewCell {让标签 = UILabel()覆盖初始化(帧:CGRect){超级初始化(帧:帧)标签.numberOfLines = 0背景颜色 = .orangecontentView.addSubview(标签)label.translatesAutoresizingMaskIntoConstraints = falselabel.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = truelabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = truelabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = truelabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true}需要初始化?(编码器 aDecoder:NSCoder){fatalError("init(coder:) 尚未实现")}覆盖 func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) ->UICollectionViewLayoutAttributes {让 layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)布局如果需要()layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizo​​ntalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)返回布局属性}}

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

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) ->UICollectionViewLayoutAttributes {让targetSize = CGSize(宽度:layoutAttributes.frame.width,高度:0)layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizo​​ntalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)返回布局属性}

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) ->UICollectionViewLayoutAttributes {label.preferredMaxLayoutWidth = layoutAttributes.frame.widthlayoutAttributes.frame.size.height = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height返回布局属性}

预期显示:

Note For 2021! See @Ely answer regarding UICollectionLayoutListConfiguration !!!!


In a vertical UICollectionView ,

Is it possible to have full-width cells, but, allow the dynamic height to be controlled by autolayout?

This strikes me as perhaps the "most important question in iOS with no really good answer."


Important:

Note that in 99% of cases, to achieve full width cells + autolayout dynamic height, simply use a table view. It's that easy.


So what's an example of where you need a collection view?

Collection views are far more powerful than table views.

One straightforward example where you must use a collection view with autolayout dynamic height:

If you animate between two layouts in a collection view. For example, between a 1 and 2 column layout, when the device rotates.

That's a common idiom in iOS. Unfortunately it can only be achieved by solving the problem posed in this QA. :-/

解决方案

1. Solution for iOS 13+

With Swift 5.1 and iOS 13, you can use Compositional Layout objects in order to solve your problem.

The following complete sample code shows how to display multiline UILabel inside full-width 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")
    }

}

Expected display:


2. Solution for iOS 11+

With Swift 5.1 and iOS 11, you can subclass UICollectionViewFlowLayout and set its estimatedItemSize property to UICollectionViewFlowLayout.automaticSize (this tells the system that you want to deal with autoresizing UICollectionViewCells). You'll then have to override layoutAttributesForElements(in:) and layoutAttributesForItem(at:) in order to set cells width. Lastly, you'll have to override your cell's preferredLayoutAttributesFitting(_:) method and compute its height.

The following complete code shows how to display multiline UILabel inside full-width UIcollectionViewCell (constrained by UICollectionView's safe area and UICollectionViewFlowLayout's insets):

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
    }

}

Here are some alternative implementations for 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
}

Expected display:

这篇关于UICollectionView,全宽单元格,允许自动布局动态高度?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆