J'ai lu de nombreux articles sur l'ajout d'en-tête à UICollectionView. Dans une application iOS 7+ dans Swift, j'essaie d'ajouter un en-tête avec un UILabel dont la hauteur doit être ajustée en fonction de la hauteur de UILabel. UILabel a des lignes = 0.
J'ai configuré l'en-tête dans IB avec AutoLayout
Le ViewController implémente UICollectionViewDelegate, UICollectionViewDataSource
. Je n'ai pas configuré de classe personnalisée pour l'en-tête, mais j'utilise ces deux fonctions:
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
}
L'affichage du texte semble fonctionner mais le calcul de la hauteur ne semble pas fonctionner (voir la capture d'écran ci-dessous). De plus, je ne pense pas non plus que je puisse accéder à UILabel via la fonction collectionView...referenceSizeForHeaderInSection
. Des suggestions sur la façon de calculer correctement CGSize?
Voici comment je l'ai fait:
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."]
L'idée de base est de créer une variable UILabel
identique à celle qui sera affichée dans l'en-tête de la section. Cette étiquette sera utilisée pour définir la taille souhaitée pour l'en-tête dans la méthode referenceSizeForHeaderInSection
.
J'ai une sortie étiquette nommée label
dans ma sous-classe UICollectionReusableView
(MyHeaderCollectionReusableView
), que j'utilise pour la vue d'en-tête de section en l'affectant dans le storyboard (en définissant "MyHeader" comme identificateur de réutilisation pour la vue en coupe). Cette étiquette mentionnée a les contraintes d’espace horizontal et vertical aux frontières de l’en-tête de la section afin de pouvoir effectuer une autolayout correctement.
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)
}
Comme l'interrogateur, j'avais un UICollectionView contenant un en-tête avec une seule étiquette, dont je voulais varier la hauteur. J'ai créé une extension à UILabel
pour mesurer la hauteur d'une étiquette multiligne avec une largeur connue:
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
}
}
Remarque : la syntaxe ci-dessus est celle de Swift 3.
Ensuite, j'implémente la méthode de taille d'en-tête de UICollectionViewDelegateFlowLayout
en tant que:
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
}
}
Le travail de calcul de la taille de l'en-tête est délégué à l'extension UILabel
ci-dessus. Le +16
est un décalage fixe dérivé expérimentalement (8 + 8) basé sur les marges et pouvant être obtenu par programme.
Tout ce dont vous avez besoin dans le rappel d'en-tête est simplement de définir le texte:
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()
}
L'idée est d'avoir une instance d'en-tête de modèle en mémoire pour calculer la hauteur souhaitée avant de créer la vue d'en-tête de résultat. Vous devez déplacer votre vue d'en-tête de section dans un fichier .nib distinct, configurer toutes les contraintes de mise en attente automatique et instancier le modèle dans votre méthode viewDidLoad de la manière suivante:
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
}
}
Ensuite, vous pourrez calculer la taille de l'en-tête (height dans mon exemple) dans votre méthode de délégué de présentation de flux:
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);
}
Et puis créez et retournez votre vue en-tête habituelle, comme toujours, puisque vous avez déjà calculé sa taille dans la méthode de délégation de présentation de flux:
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")
}
}
Vous avez oublié de mentionner un moment important - votre vue en-tête devrait avoir une contrainte d'autolayout sur son contentView pour s'adapter à la largeur de la vue de collection (plus ou moins les marges souhaitées).
J'ai eu de la chance en utilisant la méthode de Vladimir, mais je devais définir le cadre de la vue des modèles afin que sa largeur soit égale à celle de ma collection.
templateHeader.bounds = CGRectMake(templateHeader.bounds.minX, templateHeader.bounds.minY, self.collectionView.bounds.width, templateHeader.bounds.height)
De plus, mon point de vue comporte plusieurs composants redimensionnables et disposer d'une vue de modèle semble être suffisamment robuste pour gérer les modifications. Toujours se sentir comme il devrait y avoir un moyen plus facile.