Uso de layout automático en UITableViewCell que contiene una UICollectionView con carga de image asíncrona

TL; DR : la altura calculada por el motor de reproducción automática al mostrar la celda que contiene la vista de colección siempre está mal la primera vez. Llamar a reloadData() en la vista de tabla corrige el problema, pero también hace que la vista de tabla salte y sea inutilizable. ¿Alguna idea?

Explicaciones : Tengo una vista de tabla con muchas celdas diferentes de diferentes alturas.

Una de estas celdas es una galería de imágenes que puede contener una o más imágenes cargadas de forma asíncrona.

Las restricciones que tengo son las siguientes:

  • si la galería solo contiene una image y si esta image está orientada en el paisaje, la altura de la galería debe ser la altura de la image
  • De lo contrario, la galería tiene una altura fija.

Los problemas que enfrento son los siguientes:

  • Obtengo el problema Super Molesto Encapsulado-Altura-Disposition cuando la vista de tabla intenta mostrar la celda de galería la primera vez. Esta altura encapsulada siempre tiene el valor incorrecto, aunque se ha actualizado la restricción de altura en la vista de colección.

  • La vista de la tabla nunca obtiene el tamaño de la celda correctamente al primer bash.

    • Incluso si la image ya se recupera cuando se muestra la celda, la celda se muestra mal y tengo que desplazarme hacia arriba / abajo para ocultarlo, luego mostrarlo nuevamente para get el tamaño correcto … hasta la próxima vez que el tamaño de celda debe ser calculado nuevamente. Vea abajo: introduzca la descripción de la imagen aquí
  • La única forma en que puedo forzar la vista de tabla para mostrar correctamente la celda es cuando invoco a reloadData en la vista de tabla una vez que se carga la image la primera vez … lo que hace que la vista de tabla salte y que sea básicamente inutilizable.

Estoy usando Kingfisher para recuperar las imágenes, aquí está el código:

UICollectionViewCell Fuente de datos:

 func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CarouselCollectionViewCell", forIndexPath: indexPath) as! CarouselCollectionViewCell guard let collectionView = collectionView as? CarouselCollectionView else { return cell } if let imagesURLs = data.imagesURLs { let url = imagesURLs[indexPath.item] if let smallURL = url.small { KingfisherManager.shanetworkingManager.retrieveImageWithURL( smallURL, optionsInfo: KingfisherOptionsInfo(), progressBlock: nil, completionHandler: { (image, error, cacheType, imageURL) -> () in if let image = image { self.delegate?.imageIsReadyForCellAtIndexPath(image, collectionView: collectionView, screenshotIndex: indexPath.row) cell.imageView.image = image } }) } } return cell } 

Esto es lo que sucede cuando se invoca al delegado en el RooViewController en imageIsReadyForCellAtIndexPath(image: UIImage, collectionView: UICollectionView, screenshotIndex: Int) :

 func imageIsReadyForCellAtIndexPath(image: UIImage, collectionView: UICollectionView, screenshotIndex: Int) { guard let collectionView = collectionView as? CarouselCollectionView else { return } guard let collectionViewIndexPath = collectionView.indexPath else { return } guard let screenshotsCount = feed?.articles?[collectionViewIndexPath.section].content?[collectionViewIndexPath.row].data?.imagesURLs?.count else { return } let key = self.cachedSizesIndexPath(collectionViewIndexPath: collectionViewIndexPath, cellIndexPath: NSIndexPath(forItem: screenshotIndex, inSection: 0)) var sizeToCache: CGSize! if screenshotsCount == 1 { // Resize the collectionView to fit a landscape image: if image.isOrientedInLandscape { sizeToCache = image.scaleToMaxWidthAndMaxHeight(maxWidth: Constants.maxImageWidth, maxHeight: Constants.maxImageHeight) } else { sizeToCache = image.scaleToHeight(Constants.maxImageHeight) } if collectionViewCellsCachedSizesObject.dict[key] == nil { let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout let imageWidth = sizeToCache.width let sidesInset = (collectionView.frame.width - imageWidth) / 2 print("sidesInset: ", sidesInset) flowLayout.sectionInset = UIEdgeInsets(top: 0, left: sidesInset, bottom: 0, right: sidesInset) collectionViewCellsCachedSizesObject.dict[key] = sizeToCache collectionView.heightConstraint.constant = sizeToCache.height collectionView.collectionViewLayout.invalidateLayout() collectionView.setNeedsUpdateConstraints() tableView.reloadData() } } else { let sizeToCache = image.scaleToHeight(Constants.maxImageHeight) if collectionViewCellsCachedSizesObject.dict[key] == nil { // && collectionViewCellsCachedSizesObject.dict[key] != sizeToCache { collectionViewCellsCachedSizesObject.dict[key] = sizeToCache collectionView.collectionViewLayout.invalidateLayout() } } } 

Aquí es cómo configuré mi vista de colección :

 class CarouselElement: Element { let collectionView: CarouselCollectionView func cachedSizesIndexPath(collectionViewIndexPath aCollectionViewIndexPath: NSIndexPath, cellIndexPath aCellIndexPath: NSIndexPath) -> String { return "\(aCollectionViewIndexPath), \(aCellIndexPath)" } override init(style: UITableViewCellStyle, reuseIdentifier: String?) { let layout = UICollectionViewFlowLayout() layout.sectionInset = UIEdgeInsets(top: 0, left: Constants.horizontalPadding, bottom: 0, right: Constants.horizontalPadding) layout.scrollDirection = .Horizontal collectionView = CarouselCollectionView(frame: CGRectZero, collectionViewLayout: layout) collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.registerClass(CarouselCollectionViewCell.self, forCellWithReuseIdentifier: "CarouselCollectionViewCell") collectionView.allowsMultipleSelection = false collectionView.allowsSelection = true collectionView.backgroundColor = Constants.backgroundColor collectionView.showsHorizontalScrollIndicator = false super.init(style: style, reuseIdentifier: reuseIdentifier) addSubview(collectionView) addConstraints(NSLayoutConstraint.constraintsWithVisualFormat( "|[collectionView]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["collectionView":collectionView])) addConstraints(NSLayoutConstraint.constraintsWithVisualFormat( "V:|[collectionView]-verticalPadding-|", options: NSLayoutFormatOptions(), metrics: ["verticalPadding":Constants.verticalPadding], views: ["collectionView":collectionView])) collectionView.heightConstraint = NSLayoutConstraint( item: collectionView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 200) addConstraint(collectionView.heightConstraint) } requinetworking init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setCarouselDataSourceDelegate(dataSourceDelegate: CarouselDataSourceDelegate?, indexPath: NSIndexPath, cachedHeight: CGFloat?) { collectionView.indexPath = indexPath if let height = cachedHeight { collectionView.heightConstraint.constant = height } collectionView.dataSource = dataSourceDelegate collectionView.delegate = dataSourceDelegate collectionView.reloadData() } override func prepareForReuse() { super.prepareForReuse() collectionView.contentOffset = CGPointZero }} 

Y la celda personalizada que lo sostiene:

 class CarouselCollectionViewCell: UICollectionViewCell { let imageView: UIImageView override init(frame: CGRect) { imageView = UIImageView.autolayoutView() as! UIImageView imageView.image = Constants.placeholderImage imageView.contentMode = .ScaleAspectFit super.init(frame: frame) translatesAutoresizingMaskIntoConstraints = false addSubview(imageView) addConstraints( NSLayoutConstraint.constraintsWithVisualFormat( "|[imageView]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["imageView":imageView])) addConstraints( NSLayoutConstraint.constraintsWithVisualFormat( "V:|[imageView]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["imageView":imageView])) } override func prepareForReuse() { imageView.image = Constants.placeholderImage } requinetworking init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 

Por último, pero no less importante, establezco la restricción de altura de la vista de colección en tableView(_:willDisplayCell:forRowAtIndexPath:)

Intenté configurarlo también en cellForRowAtIndexPath(_:) pero no cambia nada.

Lo siento por el chunk masivo de código, pero esto me está volviendo loco.

Cuando aparece esa advertencia. Te dice que tuvo que romper una de tus limitaciones para solucionarlo. Probablemente, (la restricción de altura) para corregir esta advertencia solo hace que la prioridad de la restricción sea 999 (si era 1000).


Para actualizar la celda después de configurar la image. intentarías esto:

 dispatch_async(dispatch_get_main_queue()) { () -> Void in self.delegate?.imageIsReadyForCellAtIndexPath(image, collectionView: collectionView, screenshotIndex: indexPath.row) cell.imageView.image = image // get the content offset. let offset = self.tableView.contentOffset let visibleRect = CGRect(origin: offset, size: CGSize(width: 1, height: 1) // reload the cell tableView.beginUpdates() tableView.endUpdates() // to prevent the jumping you may scroll to the same visible rect back without animation. self.tableView.scrollRectToVisible(visibleRect, animated: false) } 

y elimine tableView.reloadData()

Por favor, dame un comentario después de que lo pruebes.