J'ai besoin d'afficher un tas de collectionViewCells qui ont des hauteurs différentes. les vues sont trop complexes et je ne veux pas calculer manuellement la hauteur attendue. Je veux imposer la mise en page automatique pour calculer la hauteur de la cellule
L'appel de dequeueReusableCellWithReuseIdentifier
en dehors de cellForItemAtIndexPath
interrompt collectionView et provoque son blocage.
Un autre problème est que la cellule ne se trouve pas dans une xib séparée. Je ne peux donc pas instancier manuellement une cellule temporaire et l'utiliser pour le calcul de la hauteur.
Des solutions pour cela?
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)
}
MODIFIER:
Un crash se produit dès que dequeueReusableCellWithReuseIdentifier est appelé. Si je n'appelle pas cette méthode et ne retourne qu'une taille, tout fonctionne correctement et les cellules s'affichent sans la taille calculée.
les tailles négatives ou nulles ne sont pas prises en charge dans la présentation du flux
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
Voici un tutoriel de Ray Wenderlich qui vous montre comment utiliser AutoLayout pour dimensionner dynamiquement UITableViewCell
s. Je pense que ce serait la même chose pour UICollectionViewCell
.
Fondamentalement, vous finissez par mettre en file d'attente, configurer une cellule prototype et saisir sa hauteur. Après avoir lu cet article, j’ai décidé de NOT implémenter cette méthode et d’écrire un code de dimensionnement clair et explicite.
Voici ce que je considère comme la "sauce secrète" de l'article complet:
- (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
}
Une fois que vous avez terminé, utilisez le code suivant:
// 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
Le code ci-dessus (avec une sous-classe UICollectionViewCell
très basique et XIB associé) me donne ceci:
Je viens de rencontrer ce problème sur UICollectionView et sur la façon dont je l'ai résolu, semblable à la réponse ci-dessus, mais d'une manière purement UICollectionView.
Créez une UICollectionViewCell personnalisée contenant tout ce que vous allez remplir pour la rendre dynamique. J'ai créé son propre fichier .xib car cela semble être l'approche la plus simple.
Ajoutez des contraintes dans ce fichier .xib permettant à la cellule d'être calculée de haut en bas. Le redimensionnement ne fonctionnera pas si vous n'avez pas pris en compte toute la hauteur. Supposons que vous ayez une vue en haut, puis une étiquette en dessous et une autre en dessous. Vous devez connecter les contraintes en haut de la cellule en haut de la vue, puis en bas de la vue en haut de la première étiquette, en bas de la première étiquette en haut de la deuxième étiquette et en bas de la deuxième étiquette. au bas de la cellule.
Chargez le fichier .xib dans le contrôleur de vue et enregistrez-le auprès de collectionView on viewDidLoad
let nib = UINib(nibName: CustomCellName, bundle: nil)
self.collectionView!.registerNib(nib, forCellWithReuseIdentifier: "customCellID")`
Chargez une deuxième copie de cette xib dans la classe et stockez-la en tant que propriété afin de pouvoir l'utiliser pour déterminer la taille de ce que cette cellule devrait être.
let sizingNibNew = NSBundle.mainBundle().loadNibNamed(CustomCellName, owner: CustomCellName.self, options: nil) as NSArray
self.sizingNibNew = (sizingNibNew.objectAtIndex(0) as? CustomViewCell)!
Implémentez la UICollectionViewFlowLayoutDelegate
dans votre contrôleur de vue. La méthode qui compte s'appelle sizeForItemAtIndexPath
. Dans cette méthode, vous devez extraire les données de la source de données associée à cette cellule à partir de chemin d'indexation. Configurez ensuite sizingCell et appelez preferredLayoutSizeFittingSize
. La méthode retourne un CGSize composé de la largeur moins les insertions de contenu et de la hauteur renvoyée par 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)
}
Dans la classe de la cellule personnalisée elle-même, vous devrez remplacer awakeFromNib
et indiquer à contentView
que sa taille doit être flexible.
override func awakeFromNib() {
super.awakeFromNib()
self.contentView.autoresizingMask = [UIViewAutoresizing.FlexibleHeight]
}
Dans la cellule personnalisée, remplacez layoutSubviews
override func layoutSubviews() {
self.layoutIfNeeded()
}
Dans la classe de la cellule personnalisée, implémentez preferredLayoutSizeFittingSize
. C’est là que vous devrez faire des tromperies sur les éléments qui sont disposés. Si c'est une étiquette, vous devrez lui indiquer quelle devrait être sa valeur preferredMaxWidth.
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
}
Toutes ces étapes devraient vous donner les bonnes tailles. Si vous obtenez 0 ou d'autres numéros géniaux que vous n'avez pas configuré vos contraintes correctement.
Nous pouvons conserver une hauteur dynamique pour la cellule de vue de collection sans xib (uniquement à l'aide d'un 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);
}
Assurez-vous que numberOfLines dans IB doit être 0.
TL; DR: Numérisez une image, puis consultez projet en cours ici.
Mise à jour de ma réponse pour une solution plus simple que j'ai trouvée.
Dans mon cas, je voulais fixer la largeur et avoir des cellules de hauteur variable. Je voulais une solution réactive réutilisable, qui gérait la rotation et ne nécessitait pas beaucoup d'intervention.
Ce à quoi je suis arrivé était que je remplaçais (seulement) systemLayoutFitting(...)
dans la cellule de la collection (dans ce cas, une classe de base pour moi) et que vaincs tout d'abord l'effort d'UICollectionView pour définir la mauvaise dimension sur contentView
en ajoutant une contrainte pour la dimension connue , la largeur.
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
puis retourne la taille finale de la cellule - utilisée pour (et cela ressemble à un bogue) la dimension de la cellule elle-même, mais pas contentView
- qui est par ailleurs contrainte à une taille en conflit (d'où la contrainte ci-dessus). Pour calculer la taille de cellule correcte, j'utilise une priorité inférieure pour la dimension que je voulais flotter et je récupère la hauteur requise pour que le contenu s'adapte à la largeur que je veux corriger:
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)
}()
}
Mais d'où vient cette largeur? Il est configuré via la variable estimatedItemSize
dans la présentation du flux de la vue de collection:
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
}()
Enfin, pour gérer la rotation, j'implémente trailCollectionDidChange
pour invalider la mise en page:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
layout.estimatedItemSize = CGSize(width: view.bounds.size.width, height: 10)
layout.invalidateLayout()
super.traitCollectionDidChange(previousTraitCollection)
}
Le résultat final ressemble à ceci:
Et j'ai publié un échantillon de travail ici.
Swift 4. *
J'ai créé un fichier Xib pour UICollectionViewCell, ce qui semble être la bonne approche.
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: Je ne recommande pas de définir l'image lors de la configuration de données dans ce cas déterminant la taille. Cela m'a donné le résultat déformé/indésirable. La configuration des textes ne m'a donné que le résultat ci-dessous.
On dirait que c'est une question très populaire, alors je vais essayer de faire mon humble contribution.
Le code ci-dessous est Swift 4 solution pour une configuration sans scénarimage. Il utilise certaines approches des réponses précédentes, par conséquent, il empêche l'avertissement de mise en forme automatique causé par la rotation du périphérique.
Je suis désolé si les exemples de code sont un peu longs. Je souhaite fournir une solution "facile à utiliser" entièrement hébergée par StackOverflow. Si vous avez des suggestions à faire à la poste, partagez l’idée et je la mettrai à jour en conséquence.
Deux classes: ViewController.Swift
et MultilineLabelCell.Swift
- Cellule contenant une seule variable 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)
}
}
Pour exécuter cet exemple de création d'un nouveau projet Xcode, créez les fichiers correspondants et remplacez le contenu de AppDelegate
par le code suivant:
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
}
}
J'ai suivi les étapes mentionnées dans ce SO et tout va bien, sauf lorsque ma Collection View contient moins de données (texte) pour les rendre suffisamment larges. En vérifiant la documentation dans systemLyaoutSizeFittingSize
, j'ai cette solution afin que ma cellule prenne la largeur demandée:
- (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;
}
J'espère que cela aiderait quelqu'un.
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize NS_AVAILABLE_IOS(6_0);
Doc Apple:
Équivalent à envoyer -systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority: avec UILayoutPriorityFittingSizeLevel pour les deux priorités.
Alors que la valeur par défaut est "assez basse" selon la documentation d'Apple:
Lorsque vous envoyez - [UIView systemLayoutSizeFittingSize:], la taille correspondant le mieux à la taille cible (l'argument) est calculée. UILayoutPriorityFittingSizeLevel est le niveau de priorité avec lequel la vue veut se conformer à la taille cible dans ce calcul. C'est assez bas. Il n’est généralement pas approprié de créer une contrainte exactement à cette priorité. Vous voulez être plus haut ou plus bas.
Donc, mon changement de comportement par défaut consiste à appliquer la largeur (ajustement horizontal) avec UILayoutPriorityRequired
.
Cela a fonctionné pour moi, espérons que vous aussi.
* Remarque: j'ai utilisé la mise en page automatique dans Nib, n'oubliez pas d'ajouter des contraintes supérieures et inférieures pour les vues secondaires dans contentView
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
}
Réponse rapide de Swift 4 sur la base de la réponse utile de @ mbm29414.
Malheureusement, cela nécessite l’utilisation d’un fichier XIB. Il ne semble pas y avoir d'alternative.
Les pièces clés utilisent une cellule de dimensionnement (créée une seule fois) et enregistrent le fichier XIB lors de l'initialisation de la vue de collection.
Ensuite, vous dimensionner chaque cellule dynamiquement dans la fonction sizeForItemAt
.
// 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
}
Suivez bolnad answer jusqu’à l’étape 4.
Simplifiez ensuite les choses en remplaçant toutes les autres étapes par:
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)
}