source how example data custom ios ios7 swift uicollectionview

ios - how - Tamaño del encabezado UICollectionView dinámico basado en UILabel



uicollectionview header swift (5)

He leído un montón de publicaciones sobre cómo agregar encabezado a UICollectionView. En una aplicación iOS 7+ en Swift, estoy tratando de agregar un encabezado con una UILabel en ella cuya altura debería ajustarse según la altura de UILabel. La UILabel tiene líneas = 0.

He configurado el encabezado en IB con AutoLayout

ViewController implementa UICollectionViewDelegate, UICollectionViewDataSource . No configuré una clase personalizada para el encabezado, pero estoy usando estas dos funciones:

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { //description is a String variable defined in the class let size:CGSize = (description as NSString).boundingRectWithSize(CGSizeMake(CGRectGetWidth(collectionView.bounds) - 20.0, 180.0), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: UIFont(name: "Helvetica Neue", size: 16.0)], context: nil).size return CGSizeMake(CGRectGetWidth(collectionView.bounds), ceil(size.height)) } func collectionView(collectionView: UICollectionView!, viewForSupplementaryElementOfKind kind: String!, atIndexPath indexPath: NSIndexPath!) -> UICollectionReusableView! { var reusableview:UICollectionReusableView = UICollectionReusableView() if (kind == UICollectionElementKindSectionHeader) { //listCollectionView is an @IBOutlet UICollectionView defined at class level, using collectionView crashes reusableview = listCollectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "ListHeader", forIndexPath: indexPath) as UICollectionReusableView let label = reusableview.viewWithTag(200) as UILabel //the UILabel within the header is tagged with 200 label.text = description //description is a String variable defined in the class } } return reusableview }

La visualización del texto parece estar funcionando pero el cálculo de la altura no parece estar funcionando (vea la captura de pantalla a continuación). Además, tampoco creo que pueda acceder a UILabel mediante la función collectionView...referenceSizeForHeaderInSection . ¿Alguna sugerencia sobre cómo calcular CGSize correctamente?


Al igual que el interrogador, tenía un UICollectionView que contenía un encabezado con una sola etiqueta, cuya altura quería variar. UILabel una extensión a UILabel para medir la altura de una etiqueta multilínea con un ancho conocido:

public extension UILabel { public class func size(withText text: String, forWidth width: CGFloat) -> CGSize { let measurementLabel = UILabel() measurementLabel.text = text measurementLabel.numberOfLines = 0 measurementLabel.lineBreakMode = .byWordWrapping measurementLabel.translatesAutoresizingMaskIntoConstraints = false measurementLabel.widthAnchor.constraint(equalToConstant: width).isActive = true let size = measurementLabel.systemLayoutSizeFitting(UILayoutFittingCompressedSize) return size } }

Nota : lo anterior está en la sintaxis Swift 3.

Luego implemento el método de tamaño de encabezado de UICollectionViewDelegateFlowLayout como:

extension MyCollectionViewController : UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { let text = textForHeader(inSection: section) var size = UILabel.size(withAttributedText: text, forWidth: collectionView.frame.size.width) size.height = size.height + 16 return size } }

El trabajo de cálculo del tamaño del encabezado se delega a la extensión UILabel anterior. El +16 es un desplazamiento fijo derivado de manera experimental (8 + 8) que se basa en márgenes y podría obtenerse mediante programación.

Todo lo que se necesita en la devolución de llamada del encabezado es simplemente establecer el texto:

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { if kind == UICollectionElementKindSectionHeader, let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerIdentifier, for: indexPath) as? MyCollectionHeader { let text = textForHeader(inSection: section) headerView.label.text = text return headerView } return UICollectionReusableView() }


Así es como lo hice:

let labels = [ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ac lorem enim. Curabitur rhoncus efficitur quam, et pretium ipsum. Nam eu magna at velit sollicitudin fringilla nec nec nisi. Quisque nec enim et ipsum feugiat pretium. Vestibulum hendrerit arcu ut ipsum gravida, ut tincidunt justo pellentesque. Etiam lacus ligula, aliquet at lorem vel, ullamcorper commodo turpis. Nullam commodo sollicitudin mauris eu faucibus.", "Lorem ipsum dolor", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ac lorem enim. Curabitur rhoncus efficitur quam, et pretium ipsum. Nam eu magna at velit sollicitudin fringilla nec nec nisi. Quisque nec enim et ipsum feugiat pretium."]

La idea básica es crear una UILabel idéntica a la que se mostrará en el encabezado de la sección. Esa etiqueta se utilizará para establecer el tamaño deseado para el encabezado en el método referenceSizeForHeaderInSection .

Tengo una salida de etiqueta llamada label en mi subclase UICollectionReusableView ( MyHeaderCollectionReusableView ), que uso para mi vista de encabezado de sección asignándola en el guión gráfico (configurando "MyHeader" como Identificador de Reutilización para la vista de sección). Esa etiqueta mencionada tiene las restricciones de espacio horizontales y verticales a los bordes de encabezado de sección para que la auto-colocación sea correcta.

override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { return 3 } override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "MyHeader", forIndexPath: indexPath) as MyHeaderCollectionReusableView headerView.label.text = labels[indexPath.section] return headerView } func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { // that -16 is because I have 8px for left and right spacing constraints for the label. let label:UILabel = UILabel(frame: CGRectMake(0, 0, collectionView.frame.width - 16, CGFloat.max)) label.numberOfLines = 0 label.lineBreakMode = NSLineBreakMode.ByWordWrapping //here, be sure you set the font type and size that matches the one set in the storyboard label label.font = UIFont(name: "Helvetica", size: 17.0) label.text = labels[section] label.sizeToFit() // Set some extra pixels for height due to the margins of the header section. //This value should be the sum of the vertical spacing you set in the autolayout constraints for the label. + 16 worked for me as I have 8px for top and bottom constraints. return CGSize(width: collectionView.frame.width, height: label.frame.height + 16) }


La idea es tener una instancia de encabezado de plantilla en la memoria para calcular la altura deseada antes de crear la vista del encabezado de resultado. Debe mover la vista del encabezado de la sección a un archivo .nib separado, configurar todas las restricciones de reproducción automática y crear una instancia de la plantilla en su método viewDidLoad de la siguiente manera:

class MyViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { @IBOutlet var collectionView : UICollectionView? private var _templateHeader : MyHeaderView override func viewDidLoad() { super.viewDidLoad() let nib = UINib(nibName: "HeaderView", bundle:nil) self.collectionView?.registerNib(nib, forCellWithReuseIdentifier: "header_view_id") _templateHeader = nib.instantiateWithOwner(nil, options:nil)[0] as! MyHeaderView } }

Entonces podrá calcular el tamaño del encabezado (altura en mi ejemplo) en su método de delegado de diseño de flujo:

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { _templateHeader.lblTitle.text = "some title here" _templateHeader.lblDescription.text = "some long description" _templateHeader.setNeedsUpdateConstraints(); _templateHeader.updateConstraintsIfNeeded() _templateHeader.setNeedsLayout(); _templateHeader.layoutIfNeeded(); let computedSize = _templateHeader.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) return CGSizeMake(collectionView.bounds.size.width, computedSize.height); }

Y luego cree y devuelva su vista de encabezado regular como siempre, ya que ya ha calculado su tamaño en el método de delegado de diseño de flujo:

func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { switch kind { case UICollectionElementKindSectionHeader: let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "header_view_id", forIndexPath: indexPath) as! MyHeaderView headerView.lblTitle.text = "some title here" headerView.lblDescription.text = "some long description" headerView.setNeedsUpdateConstraints() headerView.updateConstraintsIfNeeded() headerView.setNeedsLayout() headerView.layoutIfNeeded() return headerView default: assert(false, "Unexpected kind") } }

Olvidó decir acerca de un momento importante: su vista de encabezado debe tener una restricción de reproducción automática en su vista de contenido para ajustarse al ancho de la vista de colección (más o menos los márgenes deseados).


Tuve suerte con el método de Vladimir, pero tuve que configurar el marco de la vista de plantilla para tener el mismo ancho que mi vista de colección.

templateHeader.bounds = CGRectMake(templateHeader.bounds.minX, templateHeader.bounds.minY, self.collectionView.bounds.width, templateHeader.bounds.height)

Además, mi vista tiene varios componentes redimensionables, y tener una vista de plantilla parece lo suficientemente robusto como para lidiar con cualquier cambio. Todavía se siente como debería haber una manera más fácil.


UICollectionViewDelegate implementar el método referenceSizeForHeaderInSection UICollectionViewDelegate .

Allí tiene que calcular la altura sin usar la etiqueta llamando a boundingRectWithSize:options:context: en la cadena con los atributos apropiados.