UICollectionView - динамическая высота ячейки?
Мне нужно отобразить кучу collectionViewCells с разной высотой. представления слишком сложны, и я не хочу вручную вычислять ожидаемую высоту. Я хочу обеспечить автоматическую компоновку для расчета высоты ячейки
Вызов dequeueReusableCellWithReuseIdentifier
вне cellForItemAtIndexPath
прерывает collectionView и вызывает его сбой
Другая проблема заключается в том, что ячейка не находится в отдельном xib, поэтому я не могу вручную создать временный экземпляр и использовать его для расчета высоты.
Любые решения для этого?
public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as UICollectionViewCell
configureCell(cell, item: items[indexPath.row])
cell.contentView.setNeedsLayout()
cell.contentView.layoutIfNeeded()
return cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
}
EDIT:
Сбой происходит, как только вызывается dequeueReusableCellWithReuseIdentifier. Если я не назову этот метод и вместо этого верну его размер, все будет отлично работать, а ячейки будут отображаться без расчетного размера
отрицательные или нулевые размеры не поддерживаются в макете потока
2015-01-26 18:24:34.231 [13383:9752256] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
*** First throw call stack:
(
0 CoreFoundation 0x00000001095aef35 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x0000000109243bb7 objc_exception_throw + 45
2 CoreFoundation 0x0000000109499f33 -[__NSArrayM objectAtIndex:] + 227
3 UIKit 0x0000000107419d9c -[UICollectionViewFlowLayout _getSizingInfos] + 842
4 UIKit 0x000000010741aca9 -[UICollectionViewFlowLayout _fetchItemsInfoForRect:] + 526
5 UIKit 0x000000010741651f -[UICollectionViewFlowLayout prepareLayout] + 257
6 UIKit 0x000000010742da10 -[UICollectionViewData _prepareToLoadData] + 67
7 UIKit 0x00000001074301c6 -[UICollectionViewData layoutAttributesForItemAtIndexPath:] + 44
8 UIKit 0x00000001073fddb1 -[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:] + 248
9 0x00000001042b824c _TFC1228BasePaginatingViewController14collectionViewfS0_FTCSo16UICollectionView6layoutCSo22UICollectionViewLayout22sizeForItemAtIndexPathCSo11NSIndexPath_VSC6CGSize + 700
10 0x00000001042b83d4 _TToFC1228BasePaginatingViewController14collectionViewfS0_FTCSo16UICollectionView6layoutCSo22UICollectionViewLayout22sizeForItemAtIndexPathCSo11NSIndexPath_VSC6CGSize + 100
11 UIKit 0x0000000107419e2e -[UICollectionViewFlowLayout _getSizingInfos] + 988
12 UIKit 0x000000010741aca9 -[UICollectionViewFlowLayout _fetchItemsInfoForRect:] + 526
13 UIKit 0x000000010741651f -[UICollectionViewFlowLayout prepareLayout] + 257
14 UIKit 0x000000010742da10 -[UICollectionViewData _prepareToLoadData] + 67
15 UIKit 0x000000010742e0e9 -[UICollectionViewData validateLayoutInRect:] + 54
16 UIKit 0x00000001073f67b8 -[UICollectionView layoutSubviews] + 170
17 UIKit 0x0000000106e3c973 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 521
18 QuartzCore 0x0000000106b0fde8 -[CALayer layoutSublayers] + 150
19 QuartzCore 0x0000000106b04a0e _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 380
20 QuartzCore 0x0000000106b0487e _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
21 QuartzCore 0x0000000106a7263e _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 242
22 QuartzCore 0x0000000106a7374a _ZN2CA11Transaction6commitEv + 390
23 QuartzCore 0x0000000106a73db5 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 89
24 CoreFoundation 0x00000001094e3dc7 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
25 CoreFoundation 0x00000001094e3d20 __CFRunLoopDoObservers + 368
26 CoreFoundation 0x00000001094d9b53 __CFRunLoopRun + 1123
27 CoreFoundation 0x00000001094d9486 CFRunLoopRunSpecific + 470
28 GraphicsServices 0x000000010be869f0 GSEventRunModal + 161
29 UIKit 0x0000000106dc3420 UIApplicationMain + 1282
30 0x000000010435c709 main + 169
31 libdyld.dylib 0x000000010a0f2145 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Ответы
Ответ 1
Вот учебник Ray Wenderlich, в котором показано, как использовать AutoLayout для динамического размера UITableViewCell
s. Я бы подумал, что это будет одинаково для UICollectionViewCell
.
В принципе, вы заканчиваете удаление и настройку прототипа ячейки и захват ее высоты. Прочитав эту статью, я решил НЕ реализовать этот метод и просто написать ясный явный код размера.
Вот что я считаю "секретным соусом" для всей статьи:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self heightForBasicCellAtIndexPath:indexPath];
}
- (CGFloat)heightForBasicCellAtIndexPath:(NSIndexPath *)indexPath {
static RWBasicCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWBasicCellIdentifier];
});
[self configureBasicCell:sizingCell atIndexPath:indexPath];
return [self calculateHeightForConfiguredSizingCell:sizingCell];
}
- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell {
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height + 1.0f; // Add 1.0f for the cell separator height
}
РЕДАКТИРОВАТЬ: Я провел некоторое исследование вашей аварии и решил, что нет никакого способа сделать это без специального XIB. Хотя это немного разочаровывает, вы должны иметь возможность вырезать и вставлять с вашей раскадровки в пользовательский пустой XIB.
Как только вы это сделаете, вы получите следующий код:
// ViewController.m
#import "ViewController.h"
#import "CollectionViewCell.h"
@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout> {
}
@property (weak, nonatomic) IBOutlet CollectionViewCell *cell;
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
[self.collectionView registerNib:[UINib nibWithNibName:@"CollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"cell"];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"viewDidAppear...");
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 50;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 10.0f;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 10.0f;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return [self sizingForRowAtIndexPath:indexPath];
}
- (CGSize)sizingForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *title = @"This is a long title that will cause some wrapping to occur. This is a long title that will cause some wrapping to occur.";
static NSString *subtitle = @"This is a long subtitle that will cause some wrapping to occur. This is a long subtitle that will cause some wrapping to occur.";
static NSString *buttonTitle = @"This is a really long button title that will cause some wrapping to occur.";
static CollectionViewCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [[NSBundle mainBundle] loadNibNamed:@"CollectionViewCell" owner:self options:nil][0];
});
[sizingCell configureWithTitle:title subtitle:[NSString stringWithFormat:@"%@: Number %d.", subtitle, (int)indexPath.row] buttonTitle:buttonTitle];
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize cellSize = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
NSLog(@"cellSize: %@", NSStringFromCGSize(cellSize));
return cellSize;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *title = @"This is a long title that will cause some wrapping to occur. This is a long title that will cause some wrapping to occur.";
static NSString *subtitle = @"This is a long subtitle that will cause some wrapping to occur. This is a long subtitle that will cause some wrapping to occur.";
static NSString *buttonTitle = @"This is a really long button title that will cause some wrapping to occur.";
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
[cell configureWithTitle:title subtitle:[NSString stringWithFormat:@"%@: Number %d.", subtitle, (int)indexPath.row] buttonTitle:buttonTitle];
return cell;
}
@end
Код выше (вместе с очень базовым подклассом UICollectionViewCell
и связанным с ним XIB) дает мне следующее:
![enter image description here]()
Ответ 2
Я просто столкнулся с этой проблемой в UICollectionView и тем, как я решил ее, аналогично приведенному выше, но чистым способом UICollectionView.
-
Создайте пользовательский UICollectionViewCell, который содержит все, что вы будете заполнять, чтобы сделать его динамичным. Я создал для себя свой собственный .xib, поскольку он выглядит как самый простой подход.
-
Добавьте ограничения в этот .xib, которые позволяют вычислять ячейку сверху вниз. Повторная калибровка не будет работать, если вы не указали всю высоту. Скажем, у вас есть вид сверху, затем надпись под ним и еще одна надпись. Вам нужно будет подключить ограничения к вершине ячейки вверху этого представления, затем к нижней части окна вверху первой метки, нижней части первой метки к верхней части второй метки и нижней части второй метки к нижней части ячейки.
-
Загрузите .xib в viewcontroller и зарегистрируйте его с помощью коллекцииView на viewDidLoad
let nib = UINib(nibName: CustomCellName, bundle: nil)
self.collectionView!.registerNib(nib, forCellWithReuseIdentifier: "customCellID")`
-
Загрузите вторую копию этого xib в класс и сохраните его как свойство, чтобы вы могли использовать его для определения размера того, что должна быть в этой ячейке
let sizingNibNew = NSBundle.mainBundle().loadNibNamed(CustomCellName, owner: CustomCellName.self, options: nil) as NSArray
self.sizingNibNew = (sizingNibNew.objectAtIndex(0) as? CustomViewCell)!
-
Внедрите UICollectionViewFlowLayoutDelegate
в свой контроллер. Метод, который имеет значение, называется sizeForItemAtIndexPath
. Внутри этого метода вам нужно будет вытащить данные из источника данных, который связан с этой ячейкой из indexPath. Затем настройте sizingCell и вызовите preferredLayoutSizeFittingSize
. Метод возвращает CGSize, который будет состоять из ширины минус вставки содержимого и высоты, возвращаемой из self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
.
override func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
guard let data = datasourceArray?[indexPath.item] else {
return CGSizeZero
}
let sectionInset = self.collectionView?.collectionViewLayout.sectionInset
let widthToSubtract = sectionInset!.left + sectionInset!.right
let requiredWidth = collectionView.bounds.size.width
let targetSize = CGSize(width: requiredWidth, height: 0)
sizingNibNew.configureCell(data as! CustomCellData, delegate: self)
let adequateSize = self.sizingNibNew.preferredLayoutSizeFittingSize(targetSize)
return CGSize(width: (self.collectionView?.bounds.width)! - widthToSubtract, height: adequateSize.height)
}
-
В классе собственной ячейки вам нужно переопределить awakeFromNib
и сообщить contentView
, что его размер должен быть гибким
override func awakeFromNib() {
super.awakeFromNib()
self.contentView.autoresizingMask = [UIViewAutoresizing.FlexibleHeight]
}
-
В пользовательском переопределении ячеек layoutSubviews
override func layoutSubviews() {
self.layoutIfNeeded()
}
-
В классе пользовательского набора ячеек preferredLayoutSizeFittingSize
. Здесь вам понадобятся какие-либо обманки на предметах, которые выложены. Если его ярлык вам нужно будет указать, какой должна быть его предпочтительнаяMaxWidth.
preferredLayoutSizeFittingSize(targetSize: CGSize)-> CGSize {
let originalFrame = self.frame
let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth
var frame = self.frame
frame.size = targetSize
self.frame = frame
self.setNeedsLayout()
self.layoutIfNeeded()
self.label.preferredMaxLayoutWidth = self.questionLabel.bounds.size.width
// calling this tells the cell to figure out a size for it based on the current items set
let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
let newSize = CGSize(width:targetSize.width, height:computedSize.height)
self.frame = originalFrame
self.questionLabel.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth
return newSize
}
Все эти шаги должны дать вам правильные размеры. Если вы получаете 0 или другие напуганные цифры, чем вы неправильно настроили свои ограничения.
Ответ 3
Мы можем поддерживать динамическую высоту для ячейки просмотра коллекции без xib (только с помощью раскадровки).
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NSAttributedString* labelString = [[NSAttributedString alloc] initWithString:@"Your long string goes here" attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0]}];
CGRect cellRect = [labelString boundingRectWithSize:CGSizeMake(cellWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin context:nil];
return CGSizeMake(cellWidth, cellRect.size.height);
}
Убедитесь, что numberOfLines в IB должно быть 0.
Ответ 4
TL; DR: Сканирование до изображения, а затем проверить рабочий проект здесь.
Обновление моего ответа для более простого решения, которое я нашел..
В моем случае я хотел зафиксировать ширину и иметь ячейки переменной высоты. Я хотел использовать многоразовое решение, которое бы обрабатывало вращение и не требовало большого вмешательства.
Я пришел к systemLayoutFitting(...)
, что переопределил (просто) systemLayoutFitting(...)
в ячейке коллекции (в данном случае это базовый класс для меня), и сначала победил попытку UICollectionView установить неправильное измерение в contentView
, добавив ограничение для известного Размерность, в данном случае ширина.
class EstimatedWidthCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
contentView.translatesAutoresizingMaskIntoConstraints = false
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
contentView.translatesAutoresizingMaskIntoConstraints = false
}
override func systemLayoutSizeFitting(
_ targetSize: CGSize, withHorizontalFittingPriority
horizontalFittingPriority: UILayoutPriority,
verticalFittingPriority: UILayoutPriority) -> CGSize {
width.constant = targetSize.width
и затем возвращает окончательный размер для ячейки - используется для (и это похоже на ошибку) измерения самой ячейки, но не contentView
- который в противном случае ограничен конфликтующим размером (отсюда и ограничение выше). Чтобы вычислить правильный размер ячейки, я использую более низкий приоритет для измерения, которое я хотел плавать, и получаю обратно высоту, необходимую для размещения содержимого в пределах ширины, которую я хочу исправить:
let size = contentView.systemLayoutSizeFitting(
CGSize(width: targetSize.width, height: 1),
withHorizontalFittingPriority: .required,
verticalFittingPriority: verticalFittingPriority)
print("\(#function) \(#line) \(targetSize) -> \(size)")
return size
}
lazy var width: NSLayoutConstraint = {
return contentView.widthAnchor
.constraint(equalToConstant: bounds.size.width)
.isActive(true)
}()
}
Но откуда эта ширина? Он настраивается с estimatedItemSize
размером элемента в макете потока представления коллекции:
lazy var collectionView: UICollectionView = {
let view = UICollectionView(frame: CGRect(), collectionViewLayout: layout)
view.backgroundColor = .cyan
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var layout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
let width = view.bounds.size.width // should adjust for inset
layout.estimatedItemSize = CGSize(width: width, height: 10)
layout.scrollDirection = .vertical
return layout
}()
Наконец, для обработки вращения я реализую trailCollectionDidChange
чтобы сделать недействительным макет:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
layout.estimatedItemSize = CGSize(width: view.bounds.size.width, height: 10)
layout.invalidateLayout()
super.traitCollectionDidChange(previousTraitCollection)
}
Конечный результат выглядит так:
![enter image description here]()
И я опубликовал рабочий образец здесь.
Ответ 5
Swift 4. *
Я создал Xib для UICollectionViewCell, который кажется хорошим подходом.
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return size(indexPath: indexPath)
}
private func size(for indexPath: IndexPath) -> CGSize {
// load cell from Xib
let cell = Bundle.main.loadNibNamed("ACollectionViewCell", owner: self, options: nil)?.first as! ACollectionViewCell
// configure cell with data in it
let data = self.data[indexPath.item]
cell.configure(withData: data)
cell.setNeedsLayout()
cell.layoutIfNeeded()
// width that you want
let width = collectionView.frame.width
let height: CGFloat = 0
let targetSize = CGSize(width: width, height: height)
// get size with width that you want and automatic height
let size = cell.contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .fittingSizeLevel)
// if you want height and width both to be dynamic use below
// let size = cell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
return size
}
}
# примечание: я не рекомендую устанавливать изображение при настройке данных в этом случае, определяющем размер. Это дало мне искаженный/нежелательный результат. Настройка текстов только дала мне результат ниже.
![enter image description here]()
Ответ 6
Кажется, это довольно популярный вопрос, поэтому я постараюсь внести свой скромный вклад.
Код ниже - решение Swift 4 для установки без раскадровки. Он использует некоторые подходы из предыдущих ответов, поэтому он предотвращает предупреждение Auto Layout, возникающее при повороте устройства.
Прошу прощения, если примеры кода немного длинны. Я хочу предоставить "простое в использовании" решение, полностью размещенное на StackOverflow. Если у вас есть какие-либо предложения к посту - поделитесь идеей, и я обновлю ее соответственно.
Настройка:
Два класса: ViewController.swift
и MultilineLabelCell.swift
- Ячейка, содержащая один UILabel
.
MultilineLabelCell.swift
import UIKit
class MultilineLabelCell: UICollectionViewCell {
static let reuseId = "MultilineLabelCellReuseId"
private let label: UILabel = UILabel(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
layer.borderColor = UIColor.red.cgColor
layer.borderWidth = 1.0
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
let labelInset = UIEdgeInsets(top: 10, left: 10, bottom: -10, right: -10)
contentView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor, constant: labelInset.top).isActive = true
label.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor, constant: labelInset.left).isActive = true
label.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor, constant: labelInset.right).isActive = true
label.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: labelInset.bottom).isActive = true
label.layer.borderColor = UIColor.black.cgColor
label.layer.borderWidth = 1.0
}
required init?(coder aDecoder: NSCoder) {
fatalError("Storyboards are quicker, easier, more seductive. Not stronger then Code.")
}
func configure(text: String?) {
label.text = text
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
label.preferredMaxLayoutWidth = layoutAttributes.size.width - contentView.layoutMargins.left - contentView.layoutMargins.left
layoutAttributes.bounds.size.height = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
return layoutAttributes
}
}
ViewController.swift
import UIKit
let samuelQuotes = [
"Samuel says",
"Add different length strings here for better testing"
]
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private(set) var collectionView: UICollectionView
// Initializers
init() {
// Create new 'UICollectionView' and set 'UICollectionViewFlowLayout' as its layout
collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
// Create new 'UICollectionView' and set 'UICollectionViewFlowLayout' as its layout
collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Dynamic size sample"
// Register Cells
collectionView.register(MultilineLabelCell.self, forCellWithReuseIdentifier: MultilineLabelCell.reuseId)
// Add 'coolectionView' to display hierarchy and setup its appearance
view.addSubview(collectionView)
collectionView.backgroundColor = .white
collectionView.contentInsetAdjustmentBehavior = .always
collectionView.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
// Setup Autolayout constraints
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
// Setup 'dataSource' and 'delegate'
collectionView.dataSource = self
collectionView.delegate = self
(collectionView.collectionViewLayout as! UICollectionViewFlowLayout).estimatedItemSize = UICollectionViewFlowLayout.automaticSize
(collectionView.collectionViewLayout as! UICollectionViewFlowLayout).sectionInsetReference = .fromLayoutMargins
}
// MARK: - UICollectionViewDataSource -
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MultilineLabelCell.reuseId, for: indexPath) as! MultilineLabelCell
cell.configure(text: samuelQuotes[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return samuelQuotes.count
}
// MARK: - UICollectionViewDelegateFlowLayout -
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let sectionInset = (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset
let referenceHeight: CGFloat = 100 // Approximate height of your cell
let referenceWidth = collectionView.safeAreaLayoutGuide.layoutFrame.width
- sectionInset.left
- sectionInset.right
- collectionView.contentInset.left
- collectionView.contentInset.right
return CGSize(width: referenceWidth, height: referenceHeight)
}
}
Для запуска этого примера создайте новый проект AppDelegate
, создайте соответствующие файлы и замените содержимое AppDelegate
следующим кодом:
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var navigationController: UINavigationController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
if let window = window {
let vc = ViewController()
navigationController = UINavigationController(rootViewController: vc)
window.rootViewController = navigationController
window.makeKeyAndVisible()
}
return true
}
}
Ответ 7
Я выполнил шаги, упомянутые в этом SO, и все в порядке, за исключением случаев, когда мой Collection View имеет меньше данных (текста), чтобы сделать его достаточно широким. Проверяя документацию в systemLyaoutSizeFittingSize
, у меня есть это решение, поэтому моя ячейка принимает ширину, как я просил:
- (CGSize)calculateSizeForSizingCell:(UICollectionViewCell *)sizingCell width:(CGFloat)width {
CGRect frame = sizingCell.frame;
frame.size.width = width;
sizingCell.frame = frame;
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize size = [sizingCell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize
withHorizontalFittingPriority:UILayoutPriorityRequired
verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
return size;
}
Надеюсь, это поможет кому-то.
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize NS_AVAILABLE_IOS(6_0);
Apple doc:
Эквивалент отправке -системеLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority: с UILayoutPriorityFittingSizeLevel для обоих приоритетов.
Пока значение по умолчанию "довольно мало" в соответствии с документом Apple:
При отправке - [UIView systemLayoutSizeFittingSize:] вычисляется размер, наиболее подходящий к размеру цели (аргументу). UILayoutPriorityFittingSizeLevel - это уровень приоритета, с которым представление хочет соответствовать целевому размеру в этом вычислении. Это довольно низко. Как правило, нецелесообразно ограничивать этот приоритет. Вы хотите быть выше или ниже.
Таким образом, мое изменение поведения по умолчанию заключается в обеспечении ширины (горизонтальной установки) с помощью UILayoutPriorityRequired
.
Ответ 8
Следуйте bolnad answer до шага 4.
Затем сделайте проще, заменив все остальные шаги:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// Configure your cell
sizingNibNew.configureCell(data as! CustomCellData, delegate: self)
// We use the full width minus insets
let width = collectionView.frame.size.width - collectionView.sectionInset.left - collectionView.sectionInset.right
// Constrain our cell to this width
let height = sizingNibNew.systemLayoutSizeFitting(CGSize(width: width, height: .infinity), withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityFittingSizeLevel).height
return CGSize(width: width, height: height)
}
Ответ 9
Это сработало для меня, надеюсь, вы тоже.
* Примечание: я использовал автоматическое расположение в Nib, не забудьте добавить верхние и нижние ограничения для подпредставлений в contentView
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = YourCollectionViewCell.instantiateFromNib()
cell.frame.size.width = collectionView.frame.width
cell.data = viewModel.data[indexPath.item]
let resizing = cell.systemLayoutSizeFitting(UILayoutFittingCompressedSize, withHorizontalFittingPriority: UILayoutPriority.required, verticalFittingPriority: UILayoutPriority.fittingSizeLevel)
return resizing
}
Ответ 10
Ответ Swift 4 на основе полезного ответа от @mbm29414.
К сожалению, это требует использования файла XIB. Там, кажется, нет альтернативы.
Ключевые части используют ячейку определения размера (создается только один раз) и регистрируют XIB при инициализации представления сбора.
Затем вы динамически sizeForItemAt
размер каждой ячейки в функции sizeForItemAt
.
// UICollectionView Vars and Constants
let CellXIBName = YouViewCell.XIBName
let CellReuseID = YouViewCell.ReuseID
var sizingCell = YouViewCell()
fileprivate func initCollectionView() {
// Connect to view controller
collectionView.dataSource = self
collectionView.delegate = self
// Register XIB
collectionView.register(UINib(nibName: CellXIBName, bundle: nil), forCellWithReuseIdentifier: CellReuseID)
// Create sizing cell for dynamically sizing cells
sizingCell = Bundle.main.loadNibNamed(CellXIBName, owner: self, options: nil)?.first as! YourViewCell
// Set scroll direction
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
collectionView.collectionViewLayout = layout
// Set properties
collectionView.alwaysBounceVertical = true
collectionView.alwaysBounceHorizontal = false
// Set top/bottom padding
collectionView.contentInset = UIEdgeInsets(top: collectionViewTopPadding, left: collectionViewSidePadding, bottom: collectionViewBottomPadding, right: collectionViewSidePadding)
// Hide scrollers
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// Get cell data and render post
let data = YourData[indexPath.row]
sizingCell.renderCell(data: data)
// Get cell size
sizingCell.setNeedsLayout()
sizingCell.layoutIfNeeded()
let cellSize = sizingCell.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
// Return cell size
return cellSize
}