J'ai un UICollectionView avec une disposition de flux et chaque cellule est un carré. Comment puis-je déterminer l'espacement entre chaque cellule sur chaque ligne? Je n'arrive pas à trouver les réglages appropriés pour cela. Je vois qu'il y a des attributs d'espacement min dans le fichier nib pour une vue de collection, mais je règle ceci à 0 et les cellules ne collent même pas.
Une autre idée?
Mise à jour: Version Swift de cette réponse: https://github.com/fanpyi/UICollectionViewLeftAlignedLayout-Swift
Prendre l’avance de @ matt j’ai modifié son code pour s’assurer que les articles sont TOUJOURS alignés. J'ai découvert que si un élément finissait sur une ligne, il serait centré par la disposition du flux. J'ai apporté les modifications suivantes pour résoudre ce problème.
Cette situation ne se produirait que si les cellules dont la largeur varie sont différentes, ce qui pourrait donner une présentation similaire à celle-ci. La dernière ligne toujours laissée s'aligne en raison du comportement de UICollectionViewFlowLayout
, le problème réside dans les éléments qui se trouvent eux-mêmes dans n'importe quelle ligne sauf la dernière.
Je voyais avec le code de @ matt.
Dans cet exemple, nous voyons que les cellules se centrent si elles se retrouvent seules sur la ligne. Le code ci-dessous assure que votre vue de collection ressemble à ceci.
#import "CWDLeftAlignedCollectionViewFlowLayout.h"
const NSInteger kMaxCellSpacing = 9;
@implementation CWDLeftAlignedCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) {
if (nil == attributes.representedElementKind) {
NSIndexPath* indexPath = attributes.indexPath;
attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame;
}
}
return attributesToReturn;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* currentItemAttributes =
[super layoutAttributesForItemAtIndexPath:indexPath];
UIEdgeInsets sectionInset = [(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout sectionInset];
if (indexPath.item == 0) { // first item of section
CGRect frame = currentItemAttributes.frame;
frame.Origin.x = sectionInset.left; // first item of the section should always be left aligned
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame;
CGFloat previousFrameRightPoint = previousFrame.Origin.x + previousFrame.size.width + kMaxCellSpacing;
CGRect currentFrame = currentItemAttributes.frame;
CGRect strecthedCurrentFrame = CGRectMake(0,
currentFrame.Origin.y,
self.collectionView.frame.size.width,
currentFrame.size.height);
if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) { // if current item is the first item on the line
// the approach here is to take the current frame, left align it to the Edge of the view
// then stretch it the width of the collection view, if it intersects with the previous frame then that means it
// is on the same line, otherwise it is on it's own new line
CGRect frame = currentItemAttributes.frame;
frame.Origin.x = sectionInset.left; // first item on the line should always be left aligned
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
CGRect frame = currentItemAttributes.frame;
frame.Origin.x = previousFrameRightPoint;
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
@end
Pour obtenir un espacement maximal entre les éléments, sous-classe UICollectionViewFlowLayout et remplacez layoutAttributesForElementsInRect:
et layoutAttributesForItemAtIndexPath:
.
Par exemple, un problème courant est le suivant: les lignes d'une vue de collection sont justifiées à droite et à gauche, à l'exception de la dernière ligne qui est justifiée à gauche. Disons que nous voulons toutes les lignes doivent être justifiées à gauche, de sorte que l'espace entre elles soit, disons, de 10 points. Voici un moyen simple (dans votre sous-classe UICollectionViewFlowLayout):
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* arr = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* atts in arr) {
if (nil == atts.representedElementKind) {
NSIndexPath* ip = atts.indexPath;
atts.frame = [self layoutAttributesForItemAtIndexPath:ip].frame;
}
}
return arr;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* atts =
[super layoutAttributesForItemAtIndexPath:indexPath];
if (indexPath.item == 0) // degenerate case 1, first item of section
return atts;
NSIndexPath* ipPrev =
[NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
CGRect fPrev = [self layoutAttributesForItemAtIndexPath:ipPrev].frame;
CGFloat rightPrev = fPrev.Origin.x + fPrev.size.width + 10;
if (atts.frame.Origin.x <= rightPrev) // degenerate case 2, first item of line
return atts;
CGRect f = atts.frame;
f.Origin.x = rightPrev;
atts.frame = f;
return atts;
}
La raison pour laquelle cela est si facile, c'est que nous n'effectuons pas vraiment la lourde tâche de la mise en page; Nous tirons parti du travail de mise en page qu'UICollectionViewFlowLayout a déjà effectué pour nous. Il a déjà décidé du nombre d'éléments à inclure dans chaque ligne. nous ne faisons que lire ces lignes et regrouper les éléments ensemble, si vous voyez ce que je veux dire.
Il y a quelques points à considérer:
Essayez de modifier l'espacement minimum dans IB, mais laissez le curseur dans ce champ. Notez que Xcode ne marque pas immédiatement le document comme modifié. Cependant, lorsque vous cliquez dans un autre champ, Xcode constate que le document a été modifié et le marque dans le navigateur de fichiers. Assurez-vous donc de cliquer ou de cliquer sur un autre champ après avoir apporté une modification.
Enregistrez votre fichier de storyboard/xib après avoir apporté une modification et veillez à reconstruire l'application. Il n’est pas difficile de passer à côté de cette étape et vous vous retrouvez à vous demander pourquoi vos modifications ne semblent pas avoir eu d’effet.
UICollectionViewFlowLayout
a une propriété minimumInteritemSpacing
, qui correspond à ce que vous définissez dans IB. Mais le délégué de la collection peut aussi avoir une méthode pour déterminer l'espacement entre les articles . Cette méthode est la propriété de la mise en page, donc si vous l'implémentez dans votre délégué, la propriété de votre mise en page ne sera pas utilisée.
N'oubliez pas que l'espacement est de minimum espacement. La présentation utilisera ce nombre (qu'il provienne de la propriété ou de la méthode déléguée) comme le plus petit espace autorisé, mais il peut utiliser un plus grand espace s'il reste de l'espace sur la ligne. Ainsi, si, par exemple, vous définissez un espacement minimal de 0, il est toujours possible de voir quelques pixels entre les éléments. Si vous voulez plus de contrôle sur la façon dont les éléments sont espacés, vous devriez probablement utiliser une mise en page différente (éventuellement une de vos propres créations).
Un peu de maths fait le tour plus facilement. Le code écrit par Chris Wagner est horrible car il appelle les attributs de présentation de chaque élément précédent. Donc, plus vous faites défiler, plus c'est lent ...
Utilisez simplement modulo comme ceci (j'utilise aussi ma valeur minimumInteritemSpacing comme valeur max):
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes* currentItemAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
NSInteger numberOfItemsPerLine = floor([self collectionViewContentSize].width / [self itemSize].width);
if (indexPath.item % numberOfItemsPerLine != 0)
{
NSInteger cellIndexInLine = (indexPath.item % numberOfItemsPerLine);
CGRect itemFrame = [currentItemAttributes frame];
itemFrame.Origin.x = ([self itemSize].width * cellIndexInLine) + ([self minimumInteritemSpacing] * cellIndexInLine);
currentItemAttributes.frame = itemFrame;
}
return currentItemAttributes;
}
Un moyen facile de justifier à gauche consiste à modifier layoutAttributesForElementsInRect: dans votre sous-classe de UICollectionViewFlowLayout:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *allLayoutAttributes = [super layoutAttributesForElementsInRect:rect];
CGRect prevFrame = CGRectMake(-FLT_MAX, -FLT_MAX, 0, 0);
for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes)
{
//fix blur
CGRect theFrame = CGRectIntegral(layoutAttributes.frame);
//left justify
if(prevFrame.Origin.x > -FLT_MAX &&
prevFrame.Origin.y >= theFrame.Origin.y &&
prevFrame.Origin.y <= theFrame.Origin.y) //workaround for float == warning
{
theFrame.Origin.x = prevFrame.Origin.x +
prevFrame.size.width +
EXACT_SPACE_BETWEEN_ITEMS;
}
prevFrame = theFrame;
layoutAttributes.frame = theFrame;
}
return allLayoutAttributes;
}
La version Swift de la solution Chris.
class PazLeftAlignedCollectionViewFlowLayout : UICollectionViewFlowLayout {
var maxCellSpacing = 14.0
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
if var attributesToReturn = super.layoutAttributesForElementsInRect(rect) as? Array<UICollectionViewLayoutAttributes> {
for attributes in attributesToReturn {
if attributes.representedElementKind == nil {
let indexPath = attributes.indexPath
attributes.frame = self.layoutAttributesForItemAtIndexPath(indexPath).frame;
}
}
return attributesToReturn;
}
return super.layoutAttributesForElementsInRect(rect)
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let currentItemAttributes = super.layoutAttributesForItemAtIndexPath(indexPath)
if let collectionViewFlowLayout = self.collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
let sectionInset = collectionViewFlowLayout.sectionInset
if (indexPath.item == 0) { // first item of section
var frame = currentItemAttributes.frame;
frame.Origin.x = sectionInset.left; // first item of the section should always be left aligned
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
let previousIndexPath = NSIndexPath(forItem:indexPath.item-1, inSection:indexPath.section)
let previousFrame = self.layoutAttributesForItemAtIndexPath(previousIndexPath).frame;
let previousFrameRightPoint = Double(previousFrame.Origin.x) + Double(previousFrame.size.width) + self.maxCellSpacing
let currentFrame = currentItemAttributes.frame
var width : CGFloat = 0.0
if let collectionViewWidth = self.collectionView?.frame.size.width {
width = collectionViewWidth
}
let strecthedCurrentFrame = CGRectMake(0,
currentFrame.Origin.y,
width,
currentFrame.size.height);
if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) { // if current item is the first item on the line
// the approach here is to take the current frame, left align it to the Edge of the view
// then stretch it the width of the collection view, if it intersects with the previous frame then that means it
// is on the same line, otherwise it is on it's own new line
var frame = currentItemAttributes.frame;
frame.Origin.x = sectionInset.left; // first item on the line should always be left aligned
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
var frame = currentItemAttributes.frame;
frame.Origin.x = CGFloat(previousFrameRightPoint)
currentItemAttributes.frame = frame;
}
return currentItemAttributes;
}
}
Pour l'utiliser, procédez comme suit:
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.collectionViewLayout = self.layout
}
var layout : PazLeftAlignedCollectionViewFlowLayout {
var layout = PazLeftAlignedCollectionViewFlowLayout()
layout.itemSize = CGSizeMake(220.0, 230.0)
layout.minimumLineSpacing = 12.0
return layout
}
Vous pouvez le faire de deux manières.
Tout d’abord, faites quelques modifications dans layoutAttributesForItem
,
récupère la disposition des attributs actuels via le layoutAttributesForItem(at: IndexPath(item: indexPath.item - 1, section: indexPath.section))?.frame
précédent.
layoutAttributesForItem (at :): cette méthode fournit des informations de présentation à la demande à la vue de collection. Vous devez le remplacer et renvoyer les attributs de présentation de l'élément à la propriété indexPath demandée.
Deuxièmement, créez de nouveaux attributs via UICollectionViewLayoutAttributes(forCellWith: indexPath)
, et organisez-les où vous le souhaitez.
Et le calcul est un peu plus grand, car il effectue le levage lourd de la mise en page.
layoutAttributesForElements (in :): dans cette méthode, vous devez renvoyer les attributs de présentation de tous les éléments du rectangle donné. Vous renvoyez les attributs à la vue de collection sous forme de tableau de UICollectionViewLayoutAttributes.
Vous pouvez vérifier My repo
Solution Clean Swift, issue d'une histoire d'évolution:
open class UICollectionViewLeftAlignedLayout: UICollectionViewFlowLayout {
open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return super.layoutAttributesForElements(in: rect)?.map { $0.representedElementKind == nil ? layoutAttributesForItem(at: $0.indexPath)! : $0 }
}
open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let currentItemAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes,
collectionView != nil else {
// should never happen
return nil
}
// if the current frame, once stretched to the full row intersects the previous frame then they are on the same row
if indexPath.item != 0,
let previousFrame = layoutAttributesForItem(at: IndexPath(item: indexPath.item - 1, section: indexPath.section))?.frame,
currentItemAttributes.frame.intersects(CGRect(x: -.infinity, y: previousFrame.Origin.y, width: .infinity, height: previousFrame.size.height)) {
// the next item on a line
currentItemAttributes.frame.Origin.x = previousFrame.Origin.x + previousFrame.size.width + evaluatedMinimumInteritemSpacingForSection(at: indexPath.section)
} else {
// the first item on a line
currentItemAttributes.frame.Origin.x = evaluatedSectionInsetForSection(at: indexPath.section).left
}
return currentItemAttributes
}
func evaluatedMinimumInteritemSpacingForSection(at section: NSInteger) -> CGFloat {
return (collectionView?.delegate as? UICollectionViewDelegateFlowLayout)?.collectionView?(collectionView!, layout: self, minimumInteritemSpacingForSectionAt: section) ?? minimumInteritemSpacing
}
func evaluatedSectionInsetForSection(at index: NSInteger) -> UIEdgeInsets {
return (collectionView?.delegate as? UICollectionViewDelegateFlowLayout)?.collectionView?(collectionView!, layout: self, insetForSectionAt: index) ?? sectionInset
}
}
Utilisation: l'espacement entre les éléments est déterminé par la fonction collectionView (_:layout:minimumInteritemSpacingForSectionAt:)
du délégué.
Je l'ai mis sur github, https://github.com/Coeur/UICollectionViewLeftAlignedLayout , où j'ai en fait ajouté une fonctionnalité de prise en charge des deux directions de défilement (horizontale et verticale).
Une version plus propre de Swift pour les personnes intéressées, basée sur la réponse de Chris Wagner:
class AlignLeftFlowLayout: UICollectionViewFlowLayout {
var maximumCellSpacing = CGFloat(9.0)
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
let attributesToReturn = super.layoutAttributesForElementsInRect(rect) as? [UICollectionViewLayoutAttributes]
for attributes in attributesToReturn ?? [] {
if attributes.representedElementKind == nil {
attributes.frame = self.layoutAttributesForItemAtIndexPath(attributes.indexPath).frame
}
}
return attributesToReturn
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let curAttributes = super.layoutAttributesForItemAtIndexPath(indexPath)
let sectionInset = (self.collectionView?.collectionViewLayout as UICollectionViewFlowLayout).sectionInset
if indexPath.item == 0 {
let f = curAttributes.frame
curAttributes.frame = CGRectMake(sectionInset.left, f.Origin.y, f.size.width, f.size.height)
return curAttributes
}
let prevIndexPath = NSIndexPath(forItem: indexPath.item-1, inSection: indexPath.section)
let prevFrame = self.layoutAttributesForItemAtIndexPath(prevIndexPath).frame
let prevFrameRightPoint = prevFrame.Origin.x + prevFrame.size.width + maximumCellSpacing
let curFrame = curAttributes.frame
let stretchedCurFrame = CGRectMake(0, curFrame.Origin.y, self.collectionView!.frame.size.width, curFrame.size.height)
if CGRectIntersectsRect(prevFrame, stretchedCurFrame) {
curAttributes.frame = CGRectMake(prevFrameRightPoint, curFrame.Origin.y, curFrame.size.width, curFrame.size.height)
} else {
curAttributes.frame = CGRectMake(sectionInset.left, curFrame.Origin.y, curFrame.size.width, curFrame.size.height)
}
return curAttributes
}
}
La voici pour NSCollectionViewFlowLayout
class LeftAlignedCollectionViewFlowLayout: NSCollectionViewFlowLayout {
var maximumCellSpacing = CGFloat(2.0)
override func layoutAttributesForElementsInRect(rect: NSRect) -> [NSCollectionViewLayoutAttributes] {
let attributesToReturn = super.layoutAttributesForElementsInRect(rect)
for attributes in attributesToReturn ?? [] {
if attributes.representedElementKind == nil {
attributes.frame = self.layoutAttributesForItemAtIndexPath(attributes.indexPath!)!.frame
}
}
return attributesToReturn
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> NSCollectionViewLayoutAttributes? {
let curAttributes = super.layoutAttributesForItemAtIndexPath(indexPath)
let sectionInset = (self.collectionView?.collectionViewLayout as! NSCollectionViewFlowLayout).sectionInset
if indexPath.item == 0 {
let f = curAttributes!.frame
curAttributes!.frame = CGRectMake(sectionInset.left, f.Origin.y, f.size.width, f.size.height)
return curAttributes
}
let prevIndexPath = NSIndexPath(forItem: indexPath.item-1, inSection: indexPath.section)
let prevFrame = self.layoutAttributesForItemAtIndexPath(prevIndexPath)!.frame
let prevFrameRightPoint = prevFrame.Origin.x + prevFrame.size.width + maximumCellSpacing
let curFrame = curAttributes!.frame
let stretchedCurFrame = CGRectMake(0, curFrame.Origin.y, self.collectionView!.frame.size.width, curFrame.size.height)
if CGRectIntersectsRect(prevFrame, stretchedCurFrame) {
curAttributes!.frame = CGRectMake(prevFrameRightPoint, curFrame.Origin.y, curFrame.size.width, curFrame.size.height)
} else {
curAttributes!.frame = CGRectMake(sectionInset.left, curFrame.Origin.y, curFrame.size.width, curFrame.size.height)
}
return curAttributes
}
}