Il semble que ce que vous demandez est un moyen d'utiliser UICollectionView pour produire une mise en page comme UITableView. Si c'est vraiment ce que vous voulez, la bonne façon de le faire est d'utiliser une sous-classe UICollectionViewLayout personnalisée (peut-être quelque chose comme SBTableLayout ).
D'un autre côté, si vous demandez vraiment s'il existe un moyen propre de le faire avec le UICollectionViewFlowLayout par défaut, alors je pense qu'il n'y a aucun moyen. Même avec les cellules auto-dimensionnantes d'iOS8, ce n'est pas simple. Le problème fondamental, comme vous le dites, est que la machinerie de la disposition des flux ne fournit aucun moyen de fixer une dimension et de laisser une autre répondre. (En outre, même si vous le pouviez, il y aurait une complexité supplémentaire liée à la nécessité de deux passes de mise en page pour dimensionner les étiquettes multilignes. Cela pourrait ne pas correspondre à la façon dont les cellules auto-dimensionnantes veulent calculer tout le dimensionnement via un appel à systemLayoutSizeFittingSize.)
Cependant, si vous souhaitez toujours créer une disposition de type tableau avec une disposition de flux, avec des cellules qui déterminent leur propre taille et répondent naturellement à la largeur de la vue de la collection, bien sûr, c'est possible. Il y a toujours la manière désordonnée. Je l'ai fait avec une "cellule de dimensionnement", c'est-à-dire une UICollectionViewCell non affichée que le contrôleur ne conserve que pour calculer la taille des cellules.
Cette approche comporte deux volets. La première partie consiste pour le délégué de vue de collection à calculer la taille de cellule correcte, en prenant la largeur de la vue de collection et en utilisant la cellule de dimensionnement pour calculer la hauteur de la cellule.
Dans votre UICollectionViewDelegateFlowLayout, vous implémentez une méthode comme celle-ci:
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
// NOTE: here is where we say we want cells to use the width of the collection view
let requiredWidth = collectionView.bounds.size.width
// NOTE: here is where we ask our sizing cell to compute what height it needs
let targetSize = CGSize(width: requiredWidth, height: 0)
/// NOTE: populate the sizing cell's contents so it can compute accurately
self.sizingCell.label.text = items[indexPath.row]
let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
return adequateSize
}
Cela obligera la vue de collection à définir la largeur de la cellule en fonction de la vue de collection englobante, mais demandera ensuite à la cellule de dimensionnement de calculer la hauteur.
La deuxième partie consiste à faire en sorte que la cellule de dimensionnement utilise ses propres contraintes AL pour calculer la hauteur. Cela peut être plus difficile qu'il ne devrait l'être, en raison de la manière dont les UILabel multilignes nécessitent effectivement un processus de mise en page en deux étapes. Le travail se fait dans la méthode preferredLayoutSizeFittingSize
, qui est comme ceci:
/*
Computes the size the cell will need to be to fit within targetSize.
targetSize should be used to pass in a width.
the returned size will have the same width, and the height which is
calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
can fit within that width.
*/
func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {
// save original frame and preferredMaxLayoutWidth
let originalFrame = self.frame
let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth
// assert: targetSize.width has the required width of the cell
// step1: set the cell.frame to use that width
var frame = self.frame
frame.size = targetSize
self.frame = frame
// step2: layout the cell
self.setNeedsLayout()
self.layoutIfNeeded()
self.label.preferredMaxLayoutWidth = self.label.bounds.size.width
// assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width
// step3: compute how tall the cell needs to be
// this causes the cell to compute the height it needs, which it does by asking the
// label what height it needs to wrap within its current bounds (which we just set).
let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
// assert: computedSize has the needed height for the cell
// Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
let newSize = CGSize(width:targetSize.width,height:computedSize.height)
// restore old frame and preferredMaxLayoutWidth
self.frame = originalFrame
self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth
return newSize
}
(Ce code est adapté de l'exemple de code Apple de l'exemple de code de la session WWDC2014 sur «Advanced Collection View».)
Quelques points à remarquer. Il utilise layoutIfNeeded () pour forcer la disposition de la cellule entière, afin de calculer et de définir la largeur de l'étiquette. Mais cela ne suffit pas. Je pense que vous devez également définir preferredMaxLayoutWidth
pour que l'étiquette utilise cette largeur avec la mise en page automatique. Et ce n'est qu'alors que vous pourrez utiliser systemLayoutSizeFittingSize
pour que la cellule calcule sa hauteur tout en tenant compte de l'étiquette.
Est-ce que j'aime cette approche? Non!! Cela semble beaucoup trop complexe et fait deux fois la mise en page. Mais tant que les performances ne deviennent pas un problème, je préfère effectuer la mise en page deux fois au moment de l'exécution plutôt que de la définir deux fois dans le code, ce qui semble être la seule autre alternative.
J'espère que finalement les cellules auto-dimensionnantes fonctionneront différemment et que tout deviendra beaucoup plus simple.
Exemple de projet le montrant au travail.
Mais pourquoi ne pas utiliser uniquement des cellules auto-dimensionnantes?
En théorie, les nouvelles fonctionnalités d'iOS8 pour les «cellules auto-dimensionnantes» devraient rendre cela inutile. Si vous avez défini une cellule avec Mise en page automatique (AL), la vue de collection doit être suffisamment intelligente pour lui permettre de se dimensionner et de se disposer correctement. En pratique, je n'ai vu aucun exemple qui a permis à cela de fonctionner avec des étiquettes multilignes. Je pense que c'est en partie parce que le mécanisme cellulaire auto-dimensionnement est encore buggy.
Mais je parie que c'est principalement à cause de la complexité habituelle de la mise en page automatique et des étiquettes, à savoir que les UILabels nécessitent un processus de mise en page en deux étapes. Je ne vois pas comment vous pouvez effectuer les deux étapes avec des cellules auto-dimensionnantes.
Et comme je l'ai dit, c'est vraiment un travail pour une mise en page différente. Cela fait partie de l'essence de la disposition de flux que de positionner les choses qui ont une taille, plutôt que de fixer une largeur et de leur permettre de choisir leur hauteur.
Et qu'en est-il de PreferredLayoutAttributesFittingAttributes:?
La preferredLayoutAttributesFittingAttributes:
méthode est un hareng rouge, je pense. C'est seulement là pour être utilisé avec le nouveau mécanisme de cellule auto-dimensionnable. Ce n'est donc pas la réponse tant que ce mécanisme n'est pas fiable.
Et quoi de neuf avec systemlayoutSizeFittingSize:?
Vous avez raison, les documents sont déroutants.
Les documents sur systemLayoutSizeFittingSize:
et systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
tous les deux suggèrent que vous ne devriez passer UILayoutFittingCompressedSize
et UILayoutFittingExpandedSize
que le targetSize
. Cependant, la signature de la méthode elle-même, les commentaires d'en-tête et le comportement des fonctions indiquent qu'elles répondent à la valeur exacte du targetSize
paramètre.
En fait, si vous définissez le UICollectionViewFlowLayoutDelegate.estimatedItemSize
, afin d'activer le nouveau mécanisme de cellule auto-dimensionnable, cette valeur semble être passée en tant que targetSize. Et UILabel.systemLayoutSizeFittingSize
semble renvoyer exactement les mêmes valeurs que UILabel.sizeThatFits
. Ceci est suspect, étant donné que l'argument to systemLayoutSizeFittingSize
est censé être une cible approximative et l'argument to sizeThatFits:
est censé être une taille maximale circonscrite.
Plus de ressources
S'il est triste de penser qu'une telle exigence de routine devrait exiger des «ressources de recherche», je pense que c'est le cas. Les bons exemples et discussions sont: