tutorial source how data collection ios swift uicollectionview

ios - source - UICollectionView-altura de celda dinámica?



uicollectionview swift 3 programmatically (10)

Necesito mostrar un montón de collectionViewCells que tienen diferentes alturas. las vistas son demasiado complejas y no quiero calcular manualmente la altura esperada. Quiero aplicar el diseño automático para calcular la altura de la celda

Llamar a dequeueReusableCellWithReuseIdentifier fuera de cellForItemAtIndexPath interrumpe collectionView y hace que se bloquee

Otro problema es que la celda no está en una xib separada, por lo que no puedo crear una instancia temporal de forma manual y usarla para calcular la altura.

¿Alguna solución para esto?

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) }

EDITAR:

El bloqueo ocurre tan pronto como se llama a dequeueReusableCellWithReuseIdentifier. Si no llamo a ese método y en su lugar devuelvo un tamaño, todo funciona muy bien y las celdas aparecen sin el tamaño calculado

tamaños negativos o cero no son compatibles con el diseño de flujo

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


Funcionó para mí, espero que tú también.

* Nota: He usado el diseño automático en Nib, recuerde agregar contracciones superiores e inferiores para subvistas en contentView

// 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 }


Siga la answer Bolnad hasta el Paso 4.

Luego, simplifíquelo reemplazando todos los demás pasos con:

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 }


Aquí hay un tutorial de Ray Wenderlich que le muestra cómo usar AutoLayout para dimensionar dinámicamente UITableViewCell s. Creo que sería lo mismo para UICollectionViewCell .

Básicamente, sin embargo, terminas eliminando y configurando una celda prototipo y tomando su altura. Después de leer este artículo, decidí NO implementar este método y escribir un código de tamaño claro y explícito.

Esto es lo que considero la "salsa secreta" para todo el artículo:

- (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 }

EDITAR: Investigué un poco sobre su bloqueo y decidí que no hay forma de hacerlo sin un XIB personalizado. Si bien eso es un poco frustrante, debería poder cortar y pegar desde su Storyboard a un XIB personalizado y vacío.

Una vez que haya hecho eso, un código como el siguiente lo pondrá en marcha:

// 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

El código anterior (junto con una subclase UICollectionViewCell muy básica y XIB asociado) me da esto:


Me encontré con este problema en un UICollectionView y la forma en que lo resolví es similar a la respuesta anterior, pero de una manera pura UICollectionView.

  1. Cree una UICollectionViewCell personalizada que contenga todo lo que va a llenar para que sea dinámica. Creé su propio .xib para él, ya que parece el enfoque más fácil.

  2. Agregue restricciones en ese .xib que permitan calcular la celda de arriba a abajo. El cambio de tamaño no funcionará si no ha tenido en cuenta toda la altura. Digamos que tiene una vista en la parte superior, luego una etiqueta debajo y otra etiqueta debajo. Debería conectar restricciones a la parte superior de la celda a la parte superior de esa vista, luego la parte inferior de la vista a la parte superior de la primera etiqueta, la parte inferior de la primera etiqueta a la parte superior de la segunda etiqueta y la parte inferior de la segunda etiqueta al fondo de la celda.

  3. Cargue el .xib en el controlador de vista y regístrelo con collectionView en viewDidLoad

    let nib = UINib(nibName: CustomCellName, bundle: nil) self.collectionView!.registerNib(nib, forCellWithReuseIdentifier: "customCellID")`

  4. Cargue una segunda copia de ese xib en la clase y almacénelo como una propiedad para que pueda usarlo para determinar el tamaño de lo que debería ser esa celda

    let sizingNibNew = NSBundle.mainBundle().loadNibNamed(CustomCellName, owner: CustomCellName.self, options: nil) as NSArray self.sizingNibNew = (sizingNibNew.objectAtIndex(0) as? CustomViewCell)!

  5. Implemente UICollectionViewFlowLayoutDelegate en su controlador de vista. El método que importa se llama sizeForItemAtIndexPath . Dentro de ese método, deberá extraer los datos de la fuente de datos asociada con esa celda desde indexPath. Luego configure sizingCell y llame a preferredLayoutSizeFittingSize . El método devuelve un CGSize que consistirá en el ancho menos las inserciones de contenido y la altura que se devuelve desde 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) }

  6. En la clase de la celda personalizada, tendrá que anular awakeFromNib y decirle a contentView que su tamaño debe ser flexible

    override func awakeFromNib() { super.awakeFromNib() self.contentView.autoresizingMask = [UIViewAutoresizing.FlexibleHeight] }

  7. En el diseño de anulación de celda personalizada

    override func layoutSubviews() { self.layoutIfNeeded() }

  8. En la clase de la celda personalizada, implemente preferredLayoutSizeFittingSize . Aquí es donde tendrá que hacer algún truco sobre los elementos que se están presentando. Si se trata de una etiqueta, deberá indicarle cuál debería ser su ancho máximo preferido.

    func 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 }

Todos esos pasos deberían darte los tamaños correctos. Si obtienes 0 u otros números originales que no has configurado tus restricciones correctamente.


Parece que es una pregunta bastante popular, por lo que intentaré hacer mi humilde contribución.

El siguiente código es la solución Swift 4 para la configuración sin guión gráfico. Utiliza algunos enfoques de respuestas anteriores, por lo tanto, evita la advertencia de diseño automático causada por la rotación del dispositivo.

Lo siento si las muestras de código son un poco largas. Quiero proporcionar una solución "fácil de usar" totalmente alojada por . Si tiene alguna sugerencia para la publicación, comparta la idea y la actualizaré en consecuencia.

La puesta en marcha:

Dos clases: ViewController.swift y MultilineLabelCell.swift : celda que contiene un solo 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) } }

Para ejecutar este ejemplo, cree un nuevo proyecto Xcode, cree los archivos correspondientes y reemplace los contenidos de AppDelegate con el siguiente código:

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 } }


Podemos mantener una altura dinámica para la celda de vista de colección sin xib (solo usando storyboard).

- (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); }

Asegúrese de que numberOfLines en IB debe ser 0.


Respuesta rápida 4 basada en una respuesta útil de @ mbm29414.

Desafortunadamente, requiere el uso de un archivo XIB. No parece haber una alternativa.

Las partes clave están utilizando una celda de dimensionamiento (creada solo una vez) y registrando el XIB al inicializar la vista de colección.

Luego, dimensiona cada celda dinámicamente dentro de la función sizeForItemAt .

- (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; }


Seguí los pasos mencionados en este SO y todo está bien, excepto cuando mi Vista de colección tiene menos datos (texto) para que sea lo suficientemente amplia. Verificando la documentación en systemLyaoutSizeFittingSize , tengo esta solución para que mi celda tome el ancho que solicité:

- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize NS_AVAILABLE_IOS(6_0);

Espero que esto ayude a alguien.

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) }

Documento de Apple:

Equivalente a enviar -systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority: con UILayoutPriorityFittingSizeLevel para ambas prioridades.

Si bien el valor predeterminado es "bastante bajo" según el documento de Apple:

Cuando envía - [UIView systemLayoutSizeFittingSize:], se calcula el tamaño que se ajusta más al tamaño objetivo (el argumento). UILayoutPriorityFittingSizeLevel es el nivel de prioridad con el que la vista desea ajustarse al tamaño objetivo en ese cálculo. Es bastante bajo. Por lo general, no es apropiado hacer una restricción exactamente con esta prioridad. Quieres ser más alto o más bajo.

Entonces, mi cambio de comportamiento predeterminado es imponer el ancho (ajuste horizontal) con UILayoutPriorityRequired .


TL; DR: escanee hacia abajo a la imagen y luego vea el proyecto de trabajo aquí.

Actualizando mi respuesta para una solución más simple que encontré ...

En mi caso, quería arreglar el ancho y tener celdas de altura variable. Quería una solución reutilizable que permitiera la rotación y no requiriera mucha intervención.

A lo que llegué fue a anular (solo) systemLayoutFitting(...) en la celda de colección (en este caso, una clase base para mí), y primero derrotar el esfuerzo de UICollectionView para establecer la dimensión incorrecta en contentView agregando una restricción para lo conocido dimensión, en este caso, el ancho.

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

y luego devuelve el tamaño final de la celda, utilizado para (y esto se siente como un error) la dimensión de la celda en sí, pero no contentView , que de otro modo está limitado a un tamaño conflictivo (de ahí la restricción anterior). Para calcular el tamaño de celda correcto, uso una prioridad más baja para la dimensión que quería flotar, y recupero la altura requerida para ajustar el contenido dentro del ancho al que quiero fijar:

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) }() }

¿Pero de dónde viene este ancho? Se configura a través del estimatedItemSize de artículo estimatedItemSize en el diseño de flujo de la vista de recopilación:

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 }()

Finalmente, para manejar la rotación, implemento trailCollectionDidChange para invalidar el diseño:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { layout.estimatedItemSize = CGSize(width: view.bounds.size.width, height: 10) layout.invalidateLayout() super.traitCollectionDidChange(previousTraitCollection) }

El resultado final se ve así:

Y he publicado una muestra de trabajo aquí.


Swift 4. *

He creado un Xib para UICollectionViewCell que parece ser el buen enfoque.

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 } }

#note: no recomiendo configurar la imagen cuando configuro datos en este caso determinante de tamaño. Me dio el resultado distorsionado / no deseado. Configurar textos solo me dio el siguiente resultado.