J'ai une UICollectionView
avec des cellules aléatoires. Existe-t-il une méthode qui me permet de centrer les lignes?
Voici à quoi cela ressemble par défaut:
[ x x x x x x ]
[ x x x x x x ]
[ x x ]
Voici à quoi ressemble la mise en page souhaitée:
[ x x x x x ]
[ x x x x x ]
[ x x ]
Un peu d’arrière-plan en premier - une UICollectionView
est combinée à une UICollectionViewLayout
qui détermine la manière dont les cellules sont placées dans la vue. Cela signifie qu'une vue de collection est très flexible (vous pouvez créer presque toutes les mises en page avec elle), mais cela signifie également que la modification des mises en page peut être un peu déroutante.
La création d'une classe de présentation entièrement nouvelle est complexe. Vous devez donc essayer de modifier la présentation par défaut (UICollectionViewFlowLayout
) pour obtenir votre alignement central. Pour simplifier encore les choses, vous voudrez probablement éviter de sous-classer la présentation de flux elle-même.
Voici une approche (ce n’est peut-être pas la meilleure, mais c’est la première à laquelle je puisse penser) - divisez vos cellules en deux sections, comme suit:
[ x x x x x ] <-- Section 1
[ x x x x x ] <-- Section 1
[ x x ] <-- Section 2
Cela devrait être assez simple, à condition de connaître la largeur de votre vue défilement et le nombre de cellules pouvant tenir dans chaque ligne.
Ensuite, utilisez la méthode déléguée collectionView:layout:insetForSectionAtIndex:
pour définir les marges de votre deuxième section afin qu’elle apparaisse centrée verticalement. Une fois que cela est fait, vous devez simplement vous assurer de recalculer les divisions/encarts de section appropriés afin que les orientations portrait et paysage puissent être prises en charge.
Il y a une question un peu similaire ici - Comment centrer les cellules d'un UICollectionView? - Cela rentre plus dans les détails sur les méthodes incrustées, bien que ce ne soit pas tout à fait la même chose que vous.
Je devais faire quelque chose comme ça, mais j'avais besoin de toutes les cellules de la même section. C'était assez simple d'étendre UICollectionViewFlowLayout
aux cellules centrales. J'ai fait un pod:
Si quelqu'un a un CollectionView à 2 colonnes et si le nombre d'éléments est impair, le dernier élément doit être aligné au centre. Alors utilisez ceci
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attribute in attributes) {
NSInteger itemCount = [self.collectionView.dataSource collectionView:self.collectionView
numberOfItemsInSection:attribute.indexPath.section];
if (itemCount % 2 == 1 && attribute.indexPath.item == itemCount - 1) {
CGRect originalFrame = attribute.frame;
attribute.frame = CGRectMake(self.collectionView.bounds.size.width/2-originalFrame.size.width/2,
originalFrame.Origin.y,
originalFrame.size.width,
originalFrame.size.height);
}
}
return attributes;
}
Ceci peut être réalisé avec une disposition personnalisée (relativement) simple, sous-classée à partir de UICollectionViewFlowLayout
. Voici un exemple dansSwift
:
/**
* A simple `UICollectionViewFlowLayout` subclass that would make sure the items are center-aligned in the collection view, when scrolling vertically.
*/
class UICollectionViewFlowCenterLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let suggestedAttributes = super.layoutAttributesForElementsInRect(rect) else { return nil }
guard scrollDirection == .Vertical else { return suggestedAttributes }
var newAttributes: [UICollectionViewLayoutAttributes] = []
/// We will collect items for each row in this array
var currentRowAttributes: [UICollectionViewLayoutAttributes] = []
/// We will use this variable to detect new rows when iterating over items
var yOffset:CGFloat = sectionInset.top
for attributes in suggestedAttributes {
/// If we happen to run into a new row...
if attributes.frame.Origin.y != yOffset {
/*
* Update layout of all items in the previous row and add them to the resulting array
*/
centerSingleRowWithItemsAttributes(¤tRowAttributes, rect: rect)
newAttributes += currentRowAttributes
/*
* Reset the accumulated values for the new row
*/
currentRowAttributes = []
yOffset = attributes.frame.Origin.y
}
currentRowAttributes += [attributes]
}
/*
* Update the layout of the last row.
*/
centerSingleRowWithItemsAttributes(¤tRowAttributes, rect: rect)
newAttributes += currentRowAttributes
return newAttributes
}
/**
Updates the attributes for items, so that they are center-aligned in the given rect.
- parameter attributes: Attributes of the items
- parameter rect: Bounding rect
*/
private func centerSingleRowWithItemsAttributes(inout attributes: [UICollectionViewLayoutAttributes], rect: CGRect) {
guard let item = attributes.last else { return }
let itemsCount = CGFloat(attributes.count)
let sideInsets = rect.width - (item.frame.width * itemsCount) - (minimumInteritemSpacing * (itemsCount - 1))
var leftOffset = sideInsets / 2
for attribute in attributes {
attribute.frame.Origin.x = leftOffset
leftOffset += attribute.frame.width + minimumInteritemSpacing
}
}
}
J'ai sous-classé UICollectionViewFlowLayout
- modifié le code que j'ai trouvé ici pour l'affichage de collection aligné à gauche.
Cela ressemble à ceci:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *attributesForElementsInRect = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *newAttributesForElementsInRect = [[NSMutableArray alloc] initWithCapacity:attributesForElementsInRect.count];
CGFloat leftMargin = self.sectionInset.left;
NSMutableArray *lines = [NSMutableArray array];
NSMutableArray *currLine = [NSMutableArray array];
for (UICollectionViewLayoutAttributes *attributes in attributesForElementsInRect) {
// Handle new line
BOOL newLine = attributes.frame.Origin.x <= leftMargin;
if (newLine) {
leftMargin = self.sectionInset.left; //will add outside loop
currLine = [NSMutableArray arrayWithObject:attributes];
} else {
[currLine addObject:attributes];
}
if ([lines indexOfObject:currLine] == NSNotFound) {
[lines addObject:currLine];
}
// Align to the left
CGRect newLeftAlignedFrame = attributes.frame;
newLeftAlignedFrame.Origin.x = leftMargin;
attributes.frame = newLeftAlignedFrame;
leftMargin += attributes.frame.size.width + self.minimumInteritemSpacing;
[newAttributesForElementsInRect addObject:attributes];
}
// Center left aligned lines
for (NSArray *line in lines) {
UICollectionViewLayoutAttributes *lastAttributes = line.lastObject;
CGFloat space = CGRectGetWidth(self.collectionView.frame) - CGRectGetMaxX(lastAttributes.frame);
for (UICollectionViewLayoutAttributes *attributes in line) {
CGRect newFrame = attributes.frame;
newFrame.Origin.x = newFrame.Origin.x + space / 2;
attributes.frame = newFrame;
}
}
return newAttributesForElementsInRect;
}
J'espère que ça aide quelqu'un :)
Swift 3.0 version de la classe:
class UICollectionViewFlowCenterLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let suggestedAttributes = super.layoutAttributesForElements(in: rect) else { return nil }
guard scrollDirection == .vertical else { return suggestedAttributes }
var newAttributes: [UICollectionViewLayoutAttributes] = []
var currentRowAttributes: [UICollectionViewLayoutAttributes] = []
var yOffset:CGFloat = sectionInset.top
for attributes in suggestedAttributes {
if attributes.frame.Origin.y != yOffset {
centerSingleRowWithItemsAttributes(attributes: ¤tRowAttributes, rect: rect)
newAttributes += currentRowAttributes
currentRowAttributes = []
yOffset = attributes.frame.Origin.y
}
currentRowAttributes += [attributes]
}
centerSingleRowWithItemsAttributes(attributes: ¤tRowAttributes, rect: rect)
newAttributes += currentRowAttributes
return newAttributes
}
private func centerSingleRowWithItemsAttributes( attributes: inout [UICollectionViewLayoutAttributes], rect: CGRect) {
guard let item = attributes.last else { return }
let itemsCount = CGFloat(attributes.count)
let sideInsets = rect.width - (item.frame.width * itemsCount) - (minimumInteritemSpacing * (itemsCount - 1))
var leftOffset = sideInsets / 2
for attribute in attributes {
attribute.frame.Origin.x = leftOffset
leftOffset += attribute.frame.width + minimumInteritemSpacing
}
}
}
Il suffit de lier cette mise en page de flux. Vous pouvez aligner le centre, gauche, droite aussi.
//
// CellAllignmentFlowLayout.Swift
// UICollectionView
//
// Created by rajeshkumar Lingavel on 8/11/15.
// Copyright © 2015 rajeshkumar Lingavel. All rights reserved.
//
import UIKit
enum SZAlignment:Int {
case Center,
left,
Right
}
class CellAllignmentFlowLayout: UICollectionViewFlowLayout {
var alignment:SZAlignment!
var padding:CGFloat!
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// NSArray *allAttributesInRect = [super
// layoutAttributesForElementsInRect:rect];
let allAttributesInRect:NSArray = super.layoutAttributesForElementsInRect(rect)!
var changedAttributes:NSArray = NSArray()
switch(alignment.rawValue){
case 0:
changedAttributes = alignCenter(allAttributesInRect)
case 1:
changedAttributes = alignLeft(allAttributesInRect)
case 2:
changedAttributes = alignRight(allAttributesInRect)
default:
assertionFailure("No Direction")
}
return changedAttributes as? [UICollectionViewLayoutAttributes]
}
private func alignCenter(allAttributesInRect:NSArray) -> NSArray{
let numberOfSection:Int = (self.collectionView?.numberOfSections())!
let redefiendArray = NSMutableArray()
for i in 0 ..< numberOfSection {
let thisSectionObjects = sectionObjects(allAttributesInRect, section: i)
let totalLines = numberOfLines(thisSectionObjects, section: i)
let lastrowObjects = lastRow(thisSectionObjects, numberOfRows: totalLines, section: i)
let lastRowObjectsRow = setMiddleTheLastRow(lastrowObjects)
let start = (thisSectionObjects.count - lastrowObjects.count)
for j in start..<thisSectionObjects.count{
thisSectionObjects.replaceObjectAtIndex(j, withObject: lastRowObjectsRow.objectAtIndex(j - start))
}
redefiendArray.addObjectsFromArray(thisSectionObjects as [AnyObject])
}
return redefiendArray
}
private func alignLeft(allAttributesInRect:NSArray) -> NSArray{
return allAttributesInRect;
}
private func alignRight(allAttributesInRect:NSArray) -> NSArray{
return allAttributesInRect;
}
private func getTotalLenthOftheSection(section:Int,allAttributesInRect:NSArray) -> CGFloat{
var totalLength:CGFloat = 0.0
totalLength = totalLength + (CGFloat (((self.collectionView?.numberOfItemsInSection(section))! - 1)) * padding)
for attributes in allAttributesInRect {
if(attributes.indexPath.section == section){
totalLength = totalLength + attributes.frame.width
}
}
return totalLength
}
private func numberOfLines(allAttributesInRect:NSArray,section:Int)-> Int{
var totalLines:Int = 0
for attributes in allAttributesInRect {
if(attributes.indexPath.section == section){
if (attributes.frame.Origin.x == self.sectionInset.left){
totalLines = totalLines + 1
}
}
}
return totalLines
}
private func sectionObjects(allAttributesInRect:NSArray,section:Int) -> NSMutableArray{
let objects:NSMutableArray = NSMutableArray()
for attributes in allAttributesInRect {
if(attributes.indexPath.section == section){
objects.addObject(attributes)
}
}
return objects
}
private func lastRow(allAttributesInRect:NSArray,numberOfRows:Int,section:Int) -> NSMutableArray{
var totalLines:Int = 0
let lastRowArrays:NSMutableArray = NSMutableArray()
for attributes in allAttributesInRect {
if(attributes.indexPath.section == section){
if (attributes.frame.Origin.x == self.sectionInset.left){
totalLines = totalLines + 1
if(totalLines == numberOfRows){
lastRowArrays.addObject(attributes)
}
}
else{
if(totalLines == numberOfRows){
lastRowArrays.addObject(attributes)
}
}
}
}
return lastRowArrays
}
private func setMiddleTheLastRow(lastRowAttrs:NSMutableArray)->NSMutableArray{
let redefinedValues = NSMutableArray()
let totalLengthOftheView = self.collectionView?.frame.width
var totalLenthOftheCells:CGFloat = 0.0
totalLenthOftheCells = totalLenthOftheCells + (CGFloat (lastRowAttrs.count) - 1) * padding
for attrs in lastRowAttrs{
totalLenthOftheCells = totalLenthOftheCells + attrs.frame.width
}
var initalValue = (totalLengthOftheView!/2) - (totalLenthOftheCells/2)
for i in 0..<lastRowAttrs.count {
let changeingAttribute:UICollectionViewLayoutAttributes = lastRowAttrs[i] as! UICollectionViewLayoutAttributes
var frame = changeingAttribute.frame
frame.Origin.x = initalValue
changeingAttribute.frame = frame
redefinedValues.addObject(changeingAttribute)
initalValue = initalValue + changeingAttribute.frame.width + padding
}
return redefinedValues;
}
}
J'ai réalisé la version Swift 4 de Kyle Truscott answer :
import UIKit
class CenterFlowLayout: UICollectionViewFlowLayout {
private var attrCache = [IndexPath: UICollectionViewLayoutAttributes]()
override func prepare() {
attrCache = [:]
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var updatedAttributes = [UICollectionViewLayoutAttributes]()
let sections = self.collectionView?.numberOfSections ?? 0
var section = 0
while section < sections {
let items = self.collectionView?.numberOfItems(inSection: section) ?? 0
var item = 0
while item < items {
let indexPath = IndexPath(row: item, section: section)
if let attributes = layoutAttributesForItem(at: indexPath), attributes.frame.intersects(rect) {
updatedAttributes.append(attributes)
}
let headerKind = UICollectionElementKindSectionHeader
if let headerAttributes = layoutAttributesForSupplementaryView(ofKind: headerKind, at: indexPath) {
updatedAttributes.append(headerAttributes)
}
let footerKind = UICollectionElementKindSectionFooter
if let footerAttributes = layoutAttributesForSupplementaryView(ofKind: footerKind, at: indexPath) {
updatedAttributes.append(footerAttributes)
}
item += 1
}
section += 1
}
return updatedAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
if let attributes = attrCache[indexPath] {
return attributes
}
// Find the other items in the same "row"
var rowBuddies = [UICollectionViewLayoutAttributes]()
// Calculate the available width to center stuff within
// sectionInset is NOT applicable here because a) we're centering stuff
// and b) Flow layout has arranged the cells to respect the inset. We're
// just hijacking the X position.
var collectionViewWidth: CGFloat = 0
if let collectionView = collectionView {
collectionViewWidth = collectionView.bounds.width - collectionView.contentInset.left
- collectionView.contentInset.right
}
// To find other items in the "row", we need a rect to check intersects against.
// Take the item attributes frame (from Vanilla flow layout), and stretch it out
var rowTestFrame: CGRect = super.layoutAttributesForItem(at: indexPath)?.frame ?? .zero
rowTestFrame.Origin.x = 0
rowTestFrame.size.width = collectionViewWidth
let totalRows = self.collectionView?.numberOfItems(inSection: indexPath.section) ?? 0
// From this item, work backwards to find the first item in the row
// Decrement the row index until a) we get to 0, b) we reach a previous row
var rowStartIDX = indexPath.row
while true {
let prevIDX = rowStartIDX - 1
if prevIDX < 0 {
break
}
let prevPath = IndexPath(row: prevIDX, section: indexPath.section)
let prevFrame: CGRect = super.layoutAttributesForItem(at: prevPath)?.frame ?? .zero
// If the item intersects the test frame, it's in the same row
if prevFrame.intersects(rowTestFrame) {
rowStartIDX = prevIDX
} else {
// Found previous row, escape!
break
}
}
// Now, work back UP to find the last item in the row
// For each item in the row, add it's attributes to rowBuddies
var buddyIDX = rowStartIDX
while true {
if buddyIDX > totalRows - 1 {
break
}
let buddyPath = IndexPath(row: buddyIDX, section: indexPath.section)
if let buddyAttributes = super.layoutAttributesForItem(at: buddyPath),
buddyAttributes.frame.intersects(rowTestFrame),
let buddyAttributesCopy = buddyAttributes.copy() as? UICollectionViewLayoutAttributes {
// If the item intersects the test frame, it's in the same row
rowBuddies.append(buddyAttributesCopy)
buddyIDX += 1
} else {
// Encountered next row
break
}
}
let flowDelegate = self.collectionView?.delegate as? UICollectionViewDelegateFlowLayout
let selector = #selector(UICollectionViewDelegateFlowLayout.collectionView(_:layout:minimumInteritemSpacingForSectionAt:))
let delegateSupportsInteritemSpacing = flowDelegate?.responds(to: selector) ?? false
// x-x-x-x ... sum up the interim space
var interitemSpacing = minimumInteritemSpacing
// Check for minimumInteritemSpacingForSectionAtIndex support
if let collectionView = collectionView, delegateSupportsInteritemSpacing && rowBuddies.count > 0 {
interitemSpacing = flowDelegate?.collectionView?(collectionView,
layout: self,
minimumInteritemSpacingForSectionAt: indexPath.section) ?? 0
}
let aggregateInteritemSpacing = interitemSpacing * CGFloat(rowBuddies.count - 1)
// Sum the width of all elements in the row
var aggregateItemWidths: CGFloat = 0
for itemAttributes in rowBuddies {
aggregateItemWidths += itemAttributes.frame.width
}
// Build an alignment rect
// | |x-x-x-x| |
let alignmentWidth = aggregateItemWidths + aggregateInteritemSpacing
let alignmentXOffset: CGFloat = (collectionViewWidth - alignmentWidth) / 2
// Adjust each item's position to be centered
var previousFrame: CGRect = .zero
for itemAttributes in rowBuddies {
var itemFrame = itemAttributes.frame
if previousFrame.equalTo(.zero) {
itemFrame.Origin.x = alignmentXOffset
} else {
itemFrame.Origin.x = previousFrame.maxX + interitemSpacing
}
itemAttributes.frame = itemFrame
previousFrame = itemFrame
// Finally, add it to the cache
attrCache[itemAttributes.indexPath] = itemAttributes
}
return attrCache[indexPath]
}
}