UICollection View Flow Layout Вертикальное выравнивание
По умолчанию, когда вы используете макет потока в виде коллекции, ячейки центрируются вертикально. Есть ли способ изменить это выравнивание?
![enter image description here]()
Ответы
Ответ 1
следующий код работал у меня
@interface TopAlignedCollectionViewFlowLayout : UICollectionViewFlowLayout
- (void)alignToTopForSameLineElements:(NSArray *)sameLineElements;
@end
@implementation TopAlignedCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
{
NSArray *attrs = [super layoutAttributesForElementsInRect:rect];
CGFloat baseline = -2;
NSMutableArray *sameLineElements = [NSMutableArray array];
for (UICollectionViewLayoutAttributes *element in attrs) {
if (element.representedElementCategory == UICollectionElementCategoryCell) {
CGRect frame = element.frame;
CGFloat centerY = CGRectGetMidY(frame);
if (ABS(centerY - baseline) > 1) {
baseline = centerY;
[self alignToTopForSameLineElements:sameLineElements];
[sameLineElements removeAllObjects];
}
[sameLineElements addObject:element];
}
}
[self alignToTopForSameLineElements:sameLineElements];//align one more time for the last line
return attrs;
}
- (void)alignToTopForSameLineElements:(NSArray *)sameLineElements
{
if (sameLineElements.count == 0) {
return;
}
NSArray *sorted = [sameLineElements sortedArrayUsingComparator:^NSComparisonResult(UICollectionViewLayoutAttributes *obj1, UICollectionViewLayoutAttributes *obj2) {
CGFloat height1 = obj1.frame.size.height;
CGFloat height2 = obj2.frame.size.height;
CGFloat delta = height1 - height2;
return delta == 0. ? NSOrderedSame : ABS(delta)/delta;
}];
UICollectionViewLayoutAttributes *tallest = [sorted lastObject];
[sameLineElements enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *obj, NSUInteger idx, BOOL *stop) {
obj.frame = CGRectOffset(obj.frame, 0, tallest.frame.origin.y - obj.frame.origin.y);
}];
}
@end
Ответ 2
@DongXu: Ваше решение сработало и для меня. Вот версия SWIFT, если она:
class TopAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout
{
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]?
{
if let attrs = super.layoutAttributesForElementsInRect(rect)
{
var baseline: CGFloat = -2
var sameLineElements = [UICollectionViewLayoutAttributes]()
for element in attrs
{
if element.representedElementCategory == .Cell
{
let frame = element.frame
let centerY = CGRectGetMidY(frame)
if abs(centerY - baseline) > 1
{
baseline = centerY
TopAlignedCollectionViewFlowLayout.alignToTopForSameLineElements(sameLineElements)
sameLineElements.removeAll()
}
sameLineElements.append(element)
}
}
TopAlignedCollectionViewFlowLayout.alignToTopForSameLineElements(sameLineElements) // align one more time for the last line
return attrs
}
return nil
}
private class func alignToTopForSameLineElements(sameLineElements: [UICollectionViewLayoutAttributes])
{
if sameLineElements.count < 1
{
return
}
let sorted = sameLineElements.sort { (obj1: UICollectionViewLayoutAttributes, obj2: UICollectionViewLayoutAttributes) -> Bool in
let height1 = obj1.frame.size.height
let height2 = obj2.frame.size.height
let delta = height1 - height2
return delta <= 0
}
if let tallest = sorted.last
{
for obj in sameLineElements
{
obj.frame = CGRectOffset(obj.frame, 0, tallest.frame.origin.y - obj.frame.origin.y)
}
}
}
}
Ответ 3
Swift 4 с функционально-ориентированным подходом:
class TopAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)?
.map { $0.copy() } as? [UICollectionViewLayoutAttributes]
attributes?
.reduce([CGFloat: (CGFloat, [UICollectionViewLayoutAttributes])]()) {
guard $1.representedElementCategory == .cell else { return $0 }
return $0.merging([ceil($1.center.y): ($1.frame.origin.y, [$1])]) {
($0.0 < $1.0 ? $0.0 : $1.0, $0.1 + $1.1)
}
}
.values.forEach { minY, line in
line.forEach {
$0.frame = $0.frame.offsetBy(
dx: 0,
dy: minY - $0.frame.origin.y
)
}
}
return attributes
}
}
Ответ 4
Swift 3 Version в случае, если кто-то просто хочет скопировать и вставить:
class TopAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
if let attrs = super.layoutAttributesForElements(in: rect) {
var baseline: CGFloat = -2
var sameLineElements = [UICollectionViewLayoutAttributes]()
for element in attrs {
if element.representedElementCategory == .cell {
let frame = element.frame
let centerY = frame.midY
if abs(centerY - baseline) > 1 {
baseline = centerY
alignToTopForSameLineElements(sameLineElements: sameLineElements)
sameLineElements.removeAll()
}
sameLineElements.append(element)
}
}
alignToTopForSameLineElements(sameLineElements: sameLineElements) // align one more time for the last line
return attrs
}
return nil
}
private func alignToTopForSameLineElements(sameLineElements: [UICollectionViewLayoutAttributes]) {
if sameLineElements.count < 1 { return }
let sorted = sameLineElements.sorted { (obj1: UICollectionViewLayoutAttributes, obj2: UICollectionViewLayoutAttributes) -> Bool in
let height1 = obj1.frame.size.height
let height2 = obj2.frame.size.height
let delta = height1 - height2
return delta <= 0
}
if let tallest = sorted.last {
for obj in sameLineElements {
obj.frame = obj.frame.offsetBy(dx: 0, dy: tallest.frame.origin.y - obj.frame.origin.y)
}
}
}
}
Ответ 5
Это может работать или не работать для вашей конкретной ситуации, но у меня было несколько подклассов удачи UICollectionViewFlowLayout
следующим образом:
@interface CustomFlowLayout : UICollectionViewFlowLayout
@end
@implementation CustomFlowLayout
- (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];
currentItemAttributes.frame = CGRectOffset(currentItemAttributes.frame, 0, 0.5 * CGRectGetHeight(currentItemAttributes.frame));
return currentItemAttributes;
}
@end
Ответ 6
Я использовал нечто похожее на предыдущие ответы.
В моем случае я хочу выровнять ячейки по столбцу с разной высотой.
import UIKit
class AlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
if let attributes = super.layoutAttributesForElements(in: rect) {
let sectionElements: [Int : [UICollectionViewLayoutAttributes]] = attributes
.filter {
return $0.representedElementCategory == .cell //take cells only
}.groupBy {
return $0.indexPath.section //group attributes by section
}
sectionElements.forEach { (section, elements) in
//get suplementary view (header) to align each section
let suplementaryView = attributes.first {
return $0.representedElementCategory == .supplementaryView && $0.indexPath.section == section
}
//call align method
alignToTopSameSectionElements(elements, with: suplementaryView)
}
return attributes
}
return super.layoutAttributesForElements(in: rect)
}
private func alignToTopSameSectionElements(_ elements: [UICollectionViewLayoutAttributes], with suplementaryView: UICollectionViewLayoutAttributes?) {
//group attributes by colum
let columElements: [Int : [UICollectionViewLayoutAttributes]] = elements.groupBy {
return Int($0.frame.midX)
}
columElements.enumerated().forEach { (columIndex, object) in
let columElement = object.value.sorted {
return $0.indexPath < $1.indexPath
}
columElement.enumerated().forEach { (index, element) in
var frame = element.frame
if columIndex == 0 {
frame.origin.x = minimumLineSpacing
}
switch index {
case 0:
if let suplementaryView = suplementaryView {
frame.origin.y = suplementaryView.frame.maxY
}
default:
let beforeElement = columElement[index-1]
frame.origin.y = beforeElement.frame.maxY + minimumInteritemSpacing
}
element.frame = frame
}
}
}
}
public extension Array {
func groupBy <U> (groupingFunction group: (Element) -> U) -> [U: Array] {
var result = [U: Array]()
for item in self {
let groupKey = group(item)
if result.has(groupKey) {
result[groupKey]! += [item]
} else {
result[groupKey] = [item]
}
}
return result
}
}
Это результат этого макета:
![введите описание изображения здесь]()
Ответ 7
Класс UICollectionViewFlowLayout
выводится из базового класса UICollectionViewLayout
. И если вы посмотрите на документацию для этого, вы увидите, что существует ряд методов, которые вы можете переопределить, наиболее вероятным кандидатом является layoutAttributesForItemAtIndexPath:
.
Если вы переопределите этот метод, вы можете позволить ему вызвать его супер-реализацию, а затем настроить свойства возвращаемого объекта UICollectionViewLayoutAttributes
. В частности, вам, скорее всего, придется настроить свойство frame
, чтобы переместить элемент, чтобы он больше не центрировался.
Ответ 8
Я использовал этот код (https://github.com/yoeriboven/TopAlignedCollectionViewLayout) после того, как решение DongXu не совсем сработало. Единственная модификация заключалась в том, что она изначально была предназначена для использования с сеткой, поэтому мне нужно было создать экземпляр макета с произвольно большим числом столбцов...
let collectionViewFlowLayout = YBTopAlignedCollectionViewFlowLayout(numColumns: 1000)
Ответ 9
@DongXu ответ правильный. Тем не менее, я предлагаю сделать эти вычисления в методе UICollectionViewFlowLayout
prepare()
. Это предотвратит несколько вычислений по тем же атрибутам ячейки. Более того, prepare()
лучше использовать кеширование атрибутов.
Ответ 10
@DongXu: Ваше решение сработало и для меня. Вот версия Xamarin.iOS, если это:
public class TopAlignedCollectionViewFlowLayout : UICollectionViewFlowLayout
{
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(CGRect rect)
{
if (base.LayoutAttributesForElementsInRect(rect) is UICollectionViewLayoutAttributes[] attrs)
{
// Find all the cells and group them together by the rows they appear on
var cellsGroupedByRow = attrs
.Where(attr => attr.RepresentedElementCategory == UICollectionElementCategory.Cell)
// The default flow layout aligns cells in the middle of the row.
// Thus, cells with the same Y center point are in the same row.
// Convert to int, otherwise float values can be slighty different for cells on the same row and cause bugs.
.GroupBy(attr => Convert.ToInt32(attr.Frame.GetMidY()));
foreach (var cellRowGroup in cellsGroupedByRow)
{
TopAlignCellsOnSameLine(cellRowGroup.ToArray());
}
return attrs;
}
return null;
}
private static void TopAlignCellsOnSameLine(UICollectionViewLayoutAttributes[] cells)
{
// If only 1 cell in the row its already top aligned.
if (cells.Length <= 1) return;
// The tallest cell has the correct Y value for all the other cells in the row
var tallestCell = cells.OrderByDescending(cell => cell.Frame.Height).First();
var topOfRow = tallestCell.Frame.Y;
foreach (var cell in cells)
{
if (cell.Frame.Y == topOfRow) continue;
var frame = cell.Frame;
frame.Y = topOfRow;
cell.Frame = frame;
}
}
}