Как я могу центрировать строки в UICollectionView?
У меня есть UICollectionView
со случайными ячейками. Есть ли способ, который позволяет мне центрировать строки?
Это выглядит по умолчанию:
[ x x x x x x ]
[ x x x x x x ]
[ x x ]
Вот как выглядит желаемый макет:
[ x x x x x ]
[ x x x x x ]
[ x x ]
Ответы
Ответ 1
Сначала бит фона - a UICollectionView
объединяется с UICollectionViewLayout
, который определяет, как ячейки будут помещены в представление. Это означает, что представление коллекции очень гибкое (вы можете создать с ним практически любой макет), но также означает, что модификация макетов может немного запутать.
Создание полностью нового класса макета является сложным, поэтому вместо этого вы хотите попробовать изменить макет по умолчанию (UICollectionViewFlowLayout
), чтобы получить выравнивание по центру. Чтобы сделать его еще проще, вы, вероятно, захотите избежать подклассификации самой компоновки потока.
Здесь один подход (это может быть не лучший подход, но он первый, о котором я могу думать) - разделите свои ячейки на две части, как показано ниже:
[ x x x x x ] <-- Section 1
[ x x x x x ] <-- Section 1
[ x x ] <-- Section 2
Это должно быть достаточно простым, если вы знаете ширину своего прокрутки и количество ячеек, которые могут вставляться в каждую строку.
Затем используйте метод делегата collectionView:layout:insetForSectionAtIndex:
, чтобы установить поля для второго раздела так, чтобы он выглядел вертикально по центру. Как только вы это сделаете, вам просто нужно убедиться, что вы перекомпилируете соответствующие разделы/вставки разделов, чтобы можно было поддерживать как портретные, так и ландшафтные ориентации.
Здесь есть несколько схожие вопросы - Как центрировать выравнивание ячеек UICollectionView? - это более подробно описывает методы вставки, хотя это не совсем пытаясь сделать то же, что и вы.
Ответ 2
Мне нужно было сделать что-то подобное, но мне нужны все ячейки в одном разделе. Было довольно просто расширить UICollectionViewFlowLayout
до центральных ячеек. Я сделал pod:
https://github.com/keighl/KTCenterFlowLayout
![enter image description here]()
Ответ 3
Если у кого-то есть CollectionView, который имеет 2 столбца, и если количество элементов нечетно, последний элемент должен быть выровнен по центру. Затем используйте этот
DNLastItemCenteredLayout
- (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;
}
Ответ 4
Это может быть достигнуто с помощью (относительно) простого пользовательского макета, подкласса от UICollectionViewFlowLayout
. Вот пример в Swift
:
/**
* 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
}
}
}
Ответ 5
Я подклассифицировал UICollectionViewFlowLayout
- изменил код, который я нашел здесь для просмотра влево.
- Слева выровняйте представление коллекции
- Группировать атрибуты для линейных массивов
- Для каждой строки:
- вычислить пространство в правой части
- добавьте половину пространства для каждого атрибута строки
Он выглядит следующим образом:
- (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;
}
Надеюсь, это поможет кому-то:)
Ответ 6
Просто соедините этот макет потока. Вы также можете выровнять центр, влево, вправо.
//
// 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;
}
}
Ответ 7
Swift 3.0 версия класса:
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
}
}
}
Ответ 8
Я сделал Swift 4 версию 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 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 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 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 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]
}
}