UICollection View Flow Layout Вертикальное выравнивание

Используйте string.Format с соответствующим спецификатором формата.

В этом сообщении в блоге есть много примеров: http://blogs.msdn.com/kathykam/archive/2006/03/29/564426.aspx

21
задан Josh Caswell 30 May 2013 в 20:43
поделиться

10 ответов

следующий код работал для меня

@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
26
ответ дан DongXu 30 May 2013 в 20:43
поделиться

@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)
            }
        }
    }
}
22
ответ дан FBente 30 May 2013 в 20:43
поделиться

Swift 4 с функционально-ориентированным подходом:

class TopAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)?
            .map { [110].copy() } as? [UICollectionViewLayoutAttributes]

        attributes?
            .filter { [110].representedElementCategory == .cell }
            .reduce([:]) {
                [110].merging([ceil($1.center.y): [$1]]) {
                    [110] + $1
                }
            }
            .values.forEach { line in
                let maxHeightY = line.max {
                    [110].frame.size.height < $1.frame.size.height
                }?.frame.origin.y

                line.forEach {
                    [110].frame = [110].frame.offsetBy(
                        dx: 0,
                        dy: (maxHeightY ?? [110].frame.origin.y) - [110].frame.origin.y
                    )
                }
            }

        return attributes
    }
}
11
ответ дан Fabio Felici 30 May 2013 в 20:43
поделиться

Swift 3 Version на тот случай, если кто-то просто захочет скопировать и amp; Вставить:

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)
            }
        }
    }
}
6
ответ дан funkenstrahlen 30 May 2013 в 20:43
поделиться

Это может или не может работать для вашей конкретной ситуации, но мне посчастливилось создать подклассы 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
3
ответ дан Tim Camber 30 May 2013 в 20:43
поделиться

Класс UICollectionViewFlowLayout является производным от базового класса UICollectionViewLayout. И если вы посмотрите документацию для этого , то увидите, что есть ряд методов, которые вы можете переопределить, наиболее вероятным из которых является layoutAttributesForItemAtIndexPath:.

Если вы переопределите этот метод, вы можете позволить ему вызвать его супер реализацию, а затем настроить свойства возвращенного объекта UICollectionViewLayoutAttributes. В частности, вам, вероятно, потребуется настроить свойство frame, чтобы переместить элемент, чтобы он больше не центрировался.

0
ответ дан James Holderness 30 May 2013 в 20:43
поделиться

Я использовал этот код ( https://github.com/yoeriboven/TopAlignedCollectionViewLayout ) после того, как решение DongXu не совсем сработало. Единственная модификация заключалась в том, что он изначально предназначен для использования с сеткой, поэтому мне нужно было создать макет с произвольно большим количеством столбцов ...

let collectionViewFlowLayout = YBTopAlignedCollectionViewFlowLayout(numColumns: 1000)
0
ответ дан rob5408 30 May 2013 в 20:43
поделиться

@ DongXu ответ правильный. Однако я предлагаю сделать эти расчеты в методе UICollectionViewFlowLayout prepare(). Это предотвратит множественные вычисления для атрибутов одной ячейки. Более того, prepare() - лучшее место для управления кэшем атрибутов.

0
ответ дан rafalkitta 30 May 2013 в 20:43
поделиться

@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;
        }
    }
}
0
ответ дан ryanholden8 30 May 2013 в 20:43
поделиться

Я использовал нечто похожее на предыдущие ответы. В моем случае я хочу выровнять клетки по столбам разной высоты.

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 [110].representedElementCategory == .cell //take cells only
                }.groupBy {
                    return [110].indexPath.section //group attributes by section
            }

            sectionElements.forEach { (section, elements) in
                //get suplementary view (header) to align each section
                let suplementaryView = attributes.first {
                    return [110].representedElementCategory == .supplementaryView && [110].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([110].frame.midX)
        }

        columElements.enumerated().forEach { (columIndex, object) in
            let columElement = object.value.sorted {
                return [110].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
    }
}

Это результат этого макета:

enter image description here

2
ответ дан GOrozco58 30 May 2013 в 20:43
поделиться
Другие вопросы по тегам:

Похожие вопросы: