ios UITableViewCell 内的 UICollectionView -- 动态高度?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24126708/
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 inside a UITableViewCell -- dynamic height?
提问by Shadowman
One of our application screens requires us to place a UICollectionView
inside of a UITableViewCell
. This UICollectionView
will have a dynamic number of items, resulting in a height which must be calculated dynamically as well. However, I am running into problems trying to calculate the height of the embedded UICollectionView
.
我们的一个应用程序屏幕要求我们UICollectionView
在UITableViewCell
. 这UICollectionView
将具有动态数量的项目,从而导致也必须动态计算高度。但是,我在尝试计算嵌入的UICollectionView
.
Our overarching UIViewController
was created in Storyboards and does make use of auto layout. But, I don't know how to dynamically increase the height of the UITableViewCell
based on the height of the UICollectionView
.
我们的总体UIViewController
是在 Storyboards 中创建的,并且确实使用了自动布局。但是,我不知道如何UITableViewCell
根据UICollectionView
.
Can anyone give some tips or advice on how to accomplish this?
任何人都可以就如何实现这一目标提供一些提示或建议吗?
回答by Pablo Romeu
The right answer is YES, you CANdo this.
正确的答案是YES,你CAN做到这一点。
I came across this problem some weeks ago. It is actually easier than you may think. Put your cells into NIBs (or storyboards) and pin them to let auto layout do all the work
几周前我遇到了这个问题。它实际上比您想象的要容易。将您的单元格放入 NIB(或故事板)并固定它们以让自动布局完成所有工作
Given the following structure:
鉴于以下结构:
TableView
TableViewCell
CollectionView
CollectionViewCell
CollectionViewCell
CollectionViewCell
[...variable number of cells or different cell sizes]
表视图
表视图单元格
集合视图
集合视图单元格
集合视图单元格
集合视图单元格
[...可变数量的单元格或不同的单元格大小]
The solution is to tell auto layout to compute first the collectionViewCell sizes, then the collection view contentSize, and use it as the size of your cell. This is the UIViewmethod that "does the magic":
解决方案是告诉自动布局首先计算 collectionViewCell 大小,然后是集合视图 contentSize,并将其用作单元格的大小。这是“神奇”的UIView方法:
-(void)systemLayoutSizeFittingSize:(CGSize)targetSize
withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority
verticalFittingPriority:(UILayoutPriority)verticalFittingPriority
You have to set here the size of the TableViewCell, which in your case is the CollectionView's contentSize.
您必须在此处设置 TableViewCell 的大小,在您的情况下是 CollectionView 的 contentSize。
CollectionViewCell
集合视图单元格
At the CollectionViewCellyou have to tell the cell to layout each time you change the model (e.g.: you set a UILabel with a text, then the cell has to be layout again).
在CollectionViewCell每次更改模型时,您都必须告诉单元格进行布局(例如:您使用文本设置 UILabel,然后必须再次对单元格进行布局)。
- (void)bindWithModel:(id)model {
// Do whatever you may need to bind with your data and
// tell the collection view cell's contentView to resize
[self.contentView setNeedsLayout];
}
// Other stuff here...
TableViewCell
表视图单元格
The TableViewCelldoes the magic. It has an outlet to your collectionView, enables the auto layout for collectionView cells using estimatedItemSizeof the UICollectionViewFlowLayout.
该TableViewCell确实神奇。它有一个出口,以您的的CollectionView,能够使用的CollectionView细胞自动布局estimatedItemSize的的UICollectionViewFlowLayout。
Then, the trick is to set your tableView cell's size at the systemLayoutSizeFittingSize... method. (NOTE: iOS8 or later)
然后,诀窍是在 systemLayoutSizeFittingSize... 方法中设置 tableView 单元格的大小。(注:iOS8 或更高版本)
NOTE: I tried to use the delegate cell's height method of the tableView -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
.but it's too latefor the auto layout system to compute the CollectionView contentSize and sometimes you may find wrong resized cells.
注:我试图用的tableView的代表单元的高度方法-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
。但为时已晚的自动布局系统来计算的CollectionView contentSize,有时你可能会发现错误调整大小的细胞。
@implementation TableCell
- (void)awakeFromNib {
[super awakeFromNib];
UICollectionViewFlowLayout *flow = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
// Configure the collectionView
flow.minimumInteritemSpacing = ...;
// This enables the magic of auto layout.
// Setting estimatedItemSize different to CGSizeZero
// on flow Layout enables auto layout for collectionView cells.
// https://developer.apple.com/videos/play/wwdc2014-226/
flow.estimatedItemSize = CGSizeMake(1, 1);
// Disable the scroll on your collection view
// to avoid running into multiple scroll issues.
[self.collectionView setScrollEnabled:NO];
}
- (void)bindWithModel:(id)model {
// Do your stuff here to configure the tableViewCell
// Tell the cell to redraw its contentView
[self.contentView layoutIfNeeded];
}
// THIS IS THE MOST IMPORTANT METHOD
//
// This method tells the auto layout
// You cannot calculate the collectionView content size in any other place,
// because you run into race condition issues.
// NOTE: Works for iOS 8 or later
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority verticalFittingPriority:(UILayoutPriority)verticalFittingPriority {
// With autolayout enabled on collection view's cells we need to force a collection view relayout with the shown size (width)
self.collectionView.frame = CGRectMake(0, 0, targetSize.width, MAXFLOAT);
[self.collectionView layoutIfNeeded];
// If the cell's size has to be exactly the content
// Size of the collection View, just return the
// collectionViewLayout's collectionViewContentSize.
return [self.collectionView.collectionViewLayout collectionViewContentSize];
}
// Other stuff here...
@end
TableViewController
表视图控制器
Remember to enable the auto layout system for the tableView cells at your TableViewController:
请记住在TableViewController 中为 tableView 单元启用自动布局系统:
- (void)viewDidLoad {
[super viewDidLoad];
// Enable automatic row auto layout calculations
self.tableView.rowHeight = UITableViewAutomaticDimension;
// Set the estimatedRowHeight to a non-0 value to enable auto layout.
self.tableView.estimatedRowHeight = 10;
}
CREDIT: @rbarberahelped to sort this out
信用:@rbarbera帮助解决了这个问题
回答by Igor
I think my solution is much simpler than the one proposed by @PabloRomeu.
我认为我的解决方案比@PabloRomeu 提出的解决方案简单得多。
Step 1. Create outlet from UICollectionView
to UITableViewCell subclass
, where UICollectionView
is placed. Let, it's name will be collectionView
步骤 1. 从UICollectionView
到UITableViewCell subclass
,在UICollectionView
放置的位置创建出口。让,它的名字将是collectionView
Step 2. Add in IB for UICollectionView
height constraint and create outlet to UITableViewCell subclass
too. Let, it's name will be collectionViewHeight
.
步骤 2. 添加UICollectionView
高度约束的IB并创建出口UITableViewCell subclass
。让,它的名字将是collectionViewHeight
。
Step 3. In tableView:cellForRowAtIndexPath:
add code:
步骤 3. 在tableView:cellForRowAtIndexPath:
添加代码中:
// deque a cell
cell.frame = tableView.bounds;
[cell layoutIfNeeded];
[cell.collectionView reloadData];
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height;
回答by Rivera
Both table views and collection views are UIScrollView
subclasses and thus don't like to be embedded inside another scroll view as they try to calculate content sizes, reuse cells, etc.
表视图和集合视图都是UIScrollView
子类,因此不喜欢嵌入到另一个滚动视图中,因为它们会尝试计算内容大小、重用单元格等。
I recommend you to use only a collection view for all your purposes.
我建议您仅将集合视图用于所有目的。
You can divide it in sections and "treat" some sections' layout as a table view and others as a collection view. After all there's nothing you can't achieve with a collection view that you can with a table view.
您可以将其划分为多个部分,并将某些部分的布局“处理”为表格视图,而将其他部分的布局“处理”为集合视图。毕竟,使用表视图可以实现的集合视图没有什么是您无法实现的。
If you have a basic grid layout for your collection view "parts" you can also use regular table cells to handle them. Still if you don't need iOS 5 support you should better use collection views.
如果您的集合视图“部件”有一个基本的网格布局,您还可以使用常规表格单元格来处理它们。尽管如此,如果您不需要 iOS 5 支持,您应该更好地使用集合视图。
回答by jmad8
Pablo Romeu's answer above (https://stackoverflow.com/a/33364092/2704206) helped me immensely with my issue. I had to do a few things differently, however, to get this working for my problem. First off, I didn't have to call layoutIfNeeded()
as often. I only had to call it on the collectionView
in the systemLayoutSizeFitting
function.
上面 Pablo Romeu 的回答(https://stackoverflow.com/a/33364092/2704206)对我的问题有很大帮助。但是,我必须做一些不同的事情才能解决我的问题。首先,我不必layoutIfNeeded()
经常打电话。我只需要collectionView
在systemLayoutSizeFitting
函数中调用它。
Secondly, I had auto layout constraints on my collection view in the table view cell to give it some padding. So I had to subtract the leading and trailing margins from the targetSize.width
when setting the collectionView.frame
's width. I also had to add the top and bottom margins to the return value CGSize
height.
其次,我在表视图单元格中对我的集合视图进行了自动布局约束,以给它一些填充。所以我必须targetSize.width
在设置collectionView.frame
的宽度时减去前导和尾随边距。我还必须将顶部和底部边距添加到返回值CGSize
高度。
To get these constraint constants, I had the option of either creating outlets to the constraints, hard-coding their constants, or looking them up by an identifier. I decided to go with the third option to make my custom table view cell class easily reusable. In the end, this was everything I needed to get it working:
为了获得这些约束常量,我可以选择创建约束的出口,硬编码它们的常量,或者通过标识符查找它们。我决定采用第三个选项,使我的自定义表格视图单元格类易于重用。最后,这是我让它工作所需的一切:
class CollectionTableViewCell: UITableViewCell {
// MARK: -
// MARK: Properties
@IBOutlet weak var collectionView: UICollectionView! {
didSet {
collectionViewLayout?.estimatedItemSize = CGSize(width: 1, height: 1)
selectionStyle = .none
}
}
var collectionViewLayout: UICollectionViewFlowLayout? {
return collectionView.collectionViewLayout as? UICollectionViewFlowLayout
}
// MARK: -
// MARK: UIView functions
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
collectionView.layoutIfNeeded()
let topConstraintConstant = contentView.constraint(byIdentifier: "topAnchor")?.constant ?? 0
let bottomConstraintConstant = contentView.constraint(byIdentifier: "bottomAnchor")?.constant ?? 0
let trailingConstraintConstant = contentView.constraint(byIdentifier: "trailingAnchor")?.constant ?? 0
let leadingConstraintConstant = contentView.constraint(byIdentifier: "leadingAnchor")?.constant ?? 0
collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width - trailingConstraintConstant - leadingConstraintConstant, height: 1)
let size = collectionView.collectionViewLayout.collectionViewContentSize
let newSize = CGSize(width: size.width, height: size.height + topConstraintConstant + bottomConstraintConstant)
return newSize
}
}
As a helper function to retrieve a constraint by identifier, I add the following extension:
作为通过标识符检索约束的辅助函数,我添加了以下扩展名:
extension UIView {
func constraint(byIdentifier identifier: String) -> NSLayoutConstraint? {
return constraints.first(where: { override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
collectionView.layoutIfNeeded()
collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width , height: 1)
return collectionView.collectionViewLayout.collectionViewContentSize
}
.identifier == identifier })
}
}
NOTE: You will need to set the identifier on these constraints in your storyboard, or wherever they are being created. Unless they have a 0 constant, then it doesn't matter. Also, as in Pablo's response, you will need to use UICollectionViewFlowLayout
as the layout for your collection view. Finally, make sure you link the collectionView
IBOutlet to your storyboard.
注意:您需要在情节提要中或创建它们的任何地方为这些约束设置标识符。除非他们有一个 0 常数,否则没关系。此外,在 Pablo 的回复中,您需要将其UICollectionViewFlowLayout
用作集合视图的布局。最后,确保将collectionView
IBOutlet链接到故事板。
With the custom table view cell above, I can now subclass it in any other table view cell that needs a collection view and have it implement the UICollectionViewDelegateFlowLayout
and UICollectionViewDataSource
protocols. Hope this is helpful to someone else!
使用上面的自定义表格视图单元格,我现在可以在需要集合视图的任何其他表格视图单元格中对其进行子类化,并让它实现UICollectionViewDelegateFlowLayout
和UICollectionViewDataSource
协议。希望这对其他人有帮助!
回答by SRP-Achiever
I read through all the answers. This seems to serve all cases.
我通读了所有的答案。这似乎适用于所有情况。
override func intrinsicContentSize() -> CGSize {
self.layoutIfNeeded()
var size = super.contentSize
if size.width == 0 || size.height == 0 {
// return a default size
size = CGSize(width: 600, height:44)
}
return size
}
override func reloadData() {
super.reloadData()
self.layoutIfNeeded()
self.invalidateIntrinsicContentSize()
}
回答by Dale
An alternative to Pablo Romeu's solution is to customise UICollectionView itself, rather than doing the work in table view cell.
Pablo Romeu 的解决方案的替代方案是自定义 UICollectionView 本身,而不是在表格视图单元格中进行工作。
The underlying problem is that by default a collection view has no intrinsic size and so cannot inform auto layout of the dimensions to use. You can remedy that by creating a custom subclass which does return a useful intrinsic size.
潜在的问题是,默认情况下,集合视图没有固有大小,因此无法通知要使用的尺寸的自动布局。您可以通过创建一个返回有用的内部大小的自定义子类来解决这个问题。
Create a subclass of UICollectionView and override the following methods
创建 UICollectionView 的子类并覆盖以下方法
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
{
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
(You should also override the related methods: reloadSections, reloadItemsAtIndexPaths in a similar way to reloadData())
(您还应该以与 reloadData() 类似的方式覆盖相关方法:reloadSections、reloadItemsAtIndexPaths)
Calling layoutIfNeeded forces the collection view to recalculate the content size which can then be used as the new intrinsic size.
调用 layoutIfNeeded 强制集合视图重新计算内容大小,然后可以将其用作新的内部大小。
Also, you need to explicitly handle changes to the view size (e.g. on device rotation) in the table view controller
此外,您需要在表视图控制器中显式处理视图大小的更改(例如在设备旋转时)
override func layoutSubviews() {
self.collectionViewOutlet.constant = self.postPoll.collectionViewLayout.collectionViewContentSize.height
}
回答by Osa
Easiest approach I've came up with, so far, Credits to @igor answer above,
到目前为止,我想出的最简单的方法是上面@igor 的回答,
In your tableviewcell class just insert this
在你的 tableviewcell 类中插入这个
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//do dequeue stuff
cell.frame = tableView.bounds
cell.layoutIfNeeded()
cell.collectionView.reloadData()
cell.collectionView.heightAnchor.constraint(equalToConstant: cell.collectionView.collectionViewLayout.collectionViewContentSize.height)
cell.layoutIfNeeded()
return cell
}
and of course, change the collectionviewoutlet with your outlet in the cell's class
当然,在单元格的类中使用您的插座更改 collectionviewoutlet
回答by Nazmul Hasan
I get idea from @Igorpost and invest my time to this for my project with swift
我从@Igor 的帖子中得到了想法,并迅速为我的项目投入了时间
Just past this in your
刚刚在你的
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//do dequeue stuff
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale
return cell
}
Addition:If you see your UICollectionView choppy when loading cells.
另外:如果您在加载单元格时看到 UICollectionView 断断续续。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//do dequeue stuff
//initialize the the collection view data source with the data
cell.frame = CGRect.zero
cell.layoutIfNeeded()
return cell
}
回答by Abd Al-rhman Taher Badary
I was facing the same issue recently and I almost tried every solution in the answers, some of them worked and others didn't my main concern about @PabloRomeuapproach is that if you have other contents in the cell (other than the collection view) you will have to calculate their heights and the heights of their constraints and return the result to get the auto layout right and I don't like to calculate things manually in my code. So here is the solution that worked fine for me without doing any manual calculations in my code.
我最近遇到了同样的问题,我几乎尝试了答案中的所有解决方案,其中一些解决方案有效,而另一些解决方案则不是我对@PabloRomeu方法的主要关注是,如果您在单元格中有其他内容(集合视图除外)您将不得不计算它们的高度和它们的约束高度并返回结果以获得正确的自动布局,我不喜欢在我的代码中手动计算事物。所以这里的解决方案对我来说效果很好,而无需在我的代码中进行任何手动计算。
in the cellForRow:atIndexPath
of the table view I do the following:
在cellForRow:atIndexPath
表视图中,我执行以下操作:
I think what happens here is that I force the tableview cell to adjust its height after the collection view height has been calculated. (after providing the collectionView date to the data source)
我认为这里发生的事情是我在计算集合视图高度后强制 tableview 单元格调整其高度。(将 collectionView 日期提供给数据源后)
回答by InkGolem
I would put a static method on the collection view class that will return a size based on the content it will have. Then use that method in the heightForRowAtIndexPath
to return the proper size.
我会在集合视图类上放置一个静态方法,该方法将根据它将具有的内容返回一个大小。然后在 中使用该方法heightForRowAtIndexPath
返回正确的大小。
Also note that you can get some weird behavior when you embed these kinds of viewControllers. I did it once and had some weird memory issues I never worked out.
另请注意,当您嵌入这些类型的 viewController 时,您可能会遇到一些奇怪的行为。我做过一次,遇到了一些我从未解决过的奇怪记忆问题。