ios UICollectionView,全宽单元格,是否允许自动布局动态高度?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/44187881/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UICollectionView, full width cells, allow autolayout dynamic height?
提问by Fattie
In a vertical UICollectionView
,
在垂直方向上UICollectionView
,
Is it possible to have full-width cells, but, allow the dynamic heightto be controlled by autolayout?
是否可以拥有全角单元格,但是允许自动布局控制动态高度?
This strikes me as perhaps the "most important question in iOS with no really good answer."
在我看来,这可能是“iOS 中最重要的问题,但没有很好的答案。”
Important:
重要的:
Note that in 99% of cases, to achieve full width cells + autolayout dynamic height, simply use a table view.It's that easy.
请注意,在 99% 的情况下,要实现全宽单元格 + 自动布局动态高度,只需使用表格视图。就这么简单。
So what's an example of where you need a collection view?
那么您需要一个集合视图的例子是什么?
Collection views are incredibly more powerful than table views.
集合视图比表视图更强大。
One straightforward example where you actually have touse a collection view, to achieve full width cells + autolayout dynamic height:
一个简单的示例,您实际上必须使用集合视图来实现全宽单元格 + 自动布局动态高度:
If you animatebetween two layouts in a collection view. For example, between a 1 and 2 column layout, when the device rotates.
如果您在集合视图中的两个布局之间设置动画。例如,当设备旋转时,在 1 列和 2 列布局之间。
That's a common and normal idiom in iOS. Unfortunately it can only be achieved by solving the problem posed in this QA. :-/
这是 iOS 中常见且正常的习语。不幸的是,它只能通过解决此 QA 中提出的问题来实现。:-/
采纳答案by Imanou Petit
1. Solution for iOS 13+
1.iOS 13+解决方案
With Swift 5.1 and iOS 13, you can use Compositional Layout objectsin order to solve your problem.
在 Swift 5.1 和 iOS 13 中,您可以使用Compositional Layout 对象来解决您的问题。
The following complete sample code shows how to display multiline UILabel
inside full-width UICollectionViewCell
:
以下完整的示例代码显示了如何UILabel
在 full-width 中显示多行UICollectionViewCell
:
CollectionViewController.swift
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
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
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+
2.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 UICollectionViewCell
s). 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.
使用 Swift 5.1 和 iOS 11,您可以子类化UICollectionViewFlowLayout
并将其estimatedItemSize
属性设置为UICollectionViewFlowLayout.automaticSize
(这告诉系统您要处理 autoresizing UICollectionViewCell
s)。然后,您必须覆盖layoutAttributesForElements(in:)
和layoutAttributesForItem(at:)
以设置单元格宽度。最后,您必须覆盖单元格的preferredLayoutAttributesFitting(_:)
方法并计算其高度。
The following complete code shows how to display multiline UILabel
inside full-width UIcollectionViewCell
(constrained by UICollectionView
's safe area and UICollectionViewFlowLayout
's insets):
以下完整代码显示了如何UILabel
在全角内显示多行UIcollectionViewCell
(受UICollectionView
's safe area 和UICollectionViewFlowLayout
's insets约束):
CollectionViewController.swift
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
CustomFlowLayout.swift
import UIKit
final class CustomFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ 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")
}
}
.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
HeaderView.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
}
}
CustomCell.swift
CustomCell.swift
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
}
Here are some alternative implementations for preferredLayoutAttributesFitting(_:)
:
以下是一些替代实现preferredLayoutAttributesFitting(_:)
:
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
label.preferredMaxLayoutWidth = layoutAttributes.frame.width
layoutAttributes.frame.size.height = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
return layoutAttributes
}
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
}
Expected display:
预期显示:
回答by Dhiru
Problem
问题
You are looking for automatic height and also want to have full in width,
it is not possible to get both in using UICollectionViewFlowLayoutAutomaticSize
.
您正在寻找自动高度并且还想要全宽,使用UICollectionViewFlowLayoutAutomaticSize
.
You want to do using UICollectionView
so below is the solution for you.
您想使用UICollectionView
so 下面是适合您的解决方案。
Solution
解决方案
Step-I: Calculate the expected height of Cell
Step-I: 计算 Cell 的预期高度
1.If you have only UILabel
in CollectionViewCell
than set the numberOfLines=0
and that calculated the expected height of UIlable
, pass the all three paramters
1.如果你只有UILabel
在CollectionViewCell
超过设定numberOfLines=0
和计算的预期高度UIlable
,通过这三个paramters
// this will give you the height of your Image
let heightInPoints = image.size.height
let heightInPixels = heightInPoints * image.scale
2.If your CollectionViewCell
contains only UIImageView
and if it's is supposed to be dynamic in Height than you need to get the height of UIImage
(your UIImageView
must have AspectRatio
constraints)
2.如果你CollectionViewCell
只包含UIImageView
并且如果它在高度上应该是动态的,那么你需要得到的高度UIImage
(你UIImageView
必须有AspectRatio
约束)
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)
}
3.If it contains both than calculated their height and add them together.
3.如果它包含两者,则计算它们的高度并将它们加在一起。
STEP-II: Return the Size of
CollectionViewCell
STEP-II: 返回大小
CollectionViewCell
1.Add UICollectionViewDelegateFlowLayout
delegate in your viewController
1.UICollectionViewDelegateFlowLayout
在你的 viewController 中添加委托
2.Implement the delegate method
2.实现委托方法
override func viewDidLoad() {
super.viewDidLoad()
let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
layout?.estimatedItemSize = CGSize(width: 375, height: 200) // your average cell size
}
I hope this will help you out.
我希望这会帮助你。
回答by Eric Murphey
There are couple of ways you could tackle this problem.
有几种方法可以解决这个问题。
One way is you can give the collection view flow layout an estimated size and calculating the cell size.
一种方法是您可以为集合视图流布局提供估计大小并计算单元格大小。
Note: As mentioned in the comments below, as of iOS 10 you no longer need to provide and estimated size to trigger the call to a cellsfunc preferredLayoutAttributesFitting(_ layoutAttributes:)
. Previously (iOS 9) would require you to provide an estimated size if you wanted to query a cells prefferedLayoutAttributes.
注意:正如下面的评论中提到的,从 iOS 10 开始,您不再需要提供和估计大小来触发对单元格的调用func preferredLayoutAttributesFitting(_ layoutAttributes:)
。以前(iOS 9)如果您想查询单元格 prefferedLayoutAttributes,则需要您提供估计大小。
(assuming you are using storyboards and the collection view is connected via IB)
(假设您使用的是故事板并且集合视图通过 IB 连接)
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
}
For simple cells that will usually be enough. If size is still incorrect, in the collection view cell you can override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes
, which will give you more fine grain control over the cell size. Note: You will still need to give the flow layout an estimated size.
对于简单的单元格,这通常就足够了。如果大小仍然不正确,您可以在集合视图单元格中覆盖func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes
,这将使您对单元格大小进行更精细的控制。注意:您仍然需要为流布局提供一个估计的大小。
Then override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes
to return the correct size.
然后覆盖func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes
以返回正确的大小。
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)
}
Alternatively, instead you can use a sizing cell to calculate the size of the cell in the UICollectionViewDelegateFlowLayout
.
或者,您可以使用调整大小的单元格来计算 UICollectionViewDelegateFlowLayout
.
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
While these are not perfect production ready examples, they should get you started in the right direction. I can not say this is the best practice, but this works for me, even with fairly complex cells containing multiple labels, that may or may not wrap to multiple lines.
虽然这些不是完美的生产就绪示例,但它们应该可以帮助您朝着正确的方向开始。我不能说这是最佳实践,但这对我有用,即使包含多个标签的相当复杂的单元格也可能会或可能不会换行到多行。
回答by inf1783
I found a pretty easy solution for that issue: Inside of my CollectionViewCell I got a UIView() which is actually just a background. To get full width I just set the following Anchors
我为这个问题找到了一个非常简单的解决方案:在我的 CollectionViewCell 中,我得到了一个 UIView(),它实际上只是一个背景。为了获得全宽,我只需设置以下锚点
layout.itemSize = UICollectionViewFlowLayoutAutomaticSize
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
The "magic" happens in the first line. I set the widthAnchor dynamically to the width of the screen. Also important is to subtract the insets of your CollectionView. Otherwise the cell won't show up. If you don't want to have such a background view, just make it invisible.
“魔法”发生在第一行。我将 widthAnchor 动态设置为屏幕的宽度。同样重要的是减去 CollectionView 的插图。否则单元格不会出现。如果您不想拥有这样的背景视图,只需将其设为不可见即可。
The FlowLayout uses the following settings
FlowLayout 使用以下设置
class SelfSizingCell: UICollectionViewCell {
override func awakeFromNib() {
super.awakeFromNib()
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
}
}
Result is a full width sized cell with dynamic height.
结果是具有动态高度的全宽大小的单元格。
回答by Mecid
You have to add width constraint to CollectionViewCell
您必须向 CollectionViewCell 添加宽度约束
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
}
}
回答by Abhishek Biswas
WORKING!!! Tested on IOS:12.1 Swift 4.1
在职的!!!在 IOS 上测试:12.1 Swift 4.1
I have a very simple solution that just works with no constraint breaking.
我有一个非常简单的解决方案,它可以在不破坏约束的情况下工作。
My ViewControllerClass
我的视图控制器类
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))
}
}
CustomCell class:
自定义单元类:
collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
Main ingredient is the systemLayoutSizeFittingfunction in Customcell. And also we have to set width of the view inside Cell with constraints.
主要成分是Customcell中的systemLayoutSizeFitting函数。而且我们还必须设置带有约束的 Cell 内视图的宽度。
回答by Roman
Set
estimatedItemSize
of your flow layout: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() } ... }
Define a width constraint in the cell and set it to be equal to superview's width:
collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
一套
estimatedItemSize
你的流程布局: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() } ... }
在单元格中定义宽度约束并将其设置为等于超级视图的宽度:
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 }
Tested on iOS 11.
在 iOS 11 上测试。
回答by Chris Conover
Per my comment on Eric's answer, my solution is very similar to his, but I had to add a constraint in preferredSizeFor... in order to constrain to the fixed dimension.
根据我对 Eric 回答的评论,我的解决方案与他的非常相似,但我必须在 preferredSizeFor... 中添加一个约束以约束到固定维度。
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
This question has a number of duplicates, I answered it in detail here, and provided a working sample app here.
这个问题有很多重复,我在这里详细回答了它,并在这里提供了一个工作示例应用程序。
回答by xxtesaxx
Personally I found the best ways to have a UICollectionView where AutoLayout determines the size while each Cell can have a different size is to implement the UICollectionViewDelegateFlowLayout sizeForItemAtIndexPath function while using an actual Cell to measure the size.
就我个人而言,我发现拥有 UICollectionView 的最佳方法是在使用实际 Cell 来测量大小的同时实现 UICollectionViewDelegateFlowLayout sizeForItemAtIndexPath 函数,其中 AutoLayout 确定大小,而每个 Cell 可以具有不同的大小。
I talked about this in one of my blog posts
我在我的一篇博文中谈到了这一点
Hopefully this one will help you to achieve what you want. I'm not 100% sure but I believe unlike UITableView where you can actually have a fully automatic height of cells by using AutoLayout inconjunction with
希望这将帮助您实现您想要的。我不是 100% 确定,但我相信与 UITableView 不同,在 UITableView 中,您实际上可以通过使用 AutoLayout inconjunction 来获得完全自动的单元格高度
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)
}
}
UICollectionView does not have such a way of letting AutoLayout determine the size because UICollectionViewCell does not necessarily fills the whole width of the screen.
UICollectionView 没有这样的方式让 AutoLayout 决定大小,因为 UICollectionViewCell 不一定填满屏幕的整个宽度。
But here is a question for you: If you need full screen width cells, why do you even bother using the UICollectionView over a good old UITableView which comes with the auto sizing cells?
但是这里有一个问题要问您:如果您需要全屏宽度的单元格,为什么还要费心使用 UICollectionView 而不是带有自动调整大小的单元格的旧 UITableView?
回答by Mark Suman
Not sure if this qualifies as a "really good answer", but it's what I'm using to accomplish this. My flow layout is horizontal, and I'm trying to make the width adjust with autolayout, so it's similar to your situation.
不确定这是否符合“非常好的答案”,但这是我用来完成此任务的方法。我的流程布局是水平的,我正在尝试使用自动布局调整宽度,所以它与您的情况类似。
NotificationCenter.default.post(name: NSNotification.Name("constraintMoved"), object: self, userInfo: nil)
Then in the view controller where the autolayout constraint gets modified, I fire off an NSNotification.
然后在修改自动布局约束的视图控制器中,我触发了一个 NSNotification。
// viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(handleConstraintNotification(notification:)), name: NSNotification.Name("constraintMoved"), object: nil)
In my UICollectionView subclass, I listen for that notification:
在我的 UICollectionView 子类中,我侦听该通知:
func handleConstraintNotification(notification: Notification) {
self.collectionView?.collectionViewLayout.invalidateLayout()
}
and invalidate the layout:
并使布局无效:
##代码##This causes sizeForItemAt
to be called again using the collection view's new size. In your case, it should be able to update given the new constraints available in the layout.
这会导致sizeForItemAt
使用集合视图的新大小再次调用。在您的情况下,鉴于布局中可用的新约束,它应该能够更新。