UICollection View Flow Layout Vertical Align

后端 未结 10 2136
無奈伤痛
無奈伤痛 2021-02-03 21:45

By default, when you are using the flow layout in a collection view, cells are centered vertically. Is there a way to change this alignment ?

相关标签:
10条回答
  • 2021-02-03 21:56

    @DongXu's answer is correct. However, I suggest to make those calculations in UICollectionViewFlowLayout's prepare() method. It will prevent multiple calculations on the same cell's attributes. Moreover, prepare() is better place to manage attributes cache.

    0 讨论(0)
  • 2021-02-03 22:03

    Swift 4 with functional oriented approach:

    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
        }
    }
    
    0 讨论(0)
  • 2021-02-03 22:04

    This may or may not work for your particular situation, but I had some luck subclassing UICollectionViewFlowLayout in the following way:

    @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
    
    0 讨论(0)
  • 2021-02-03 22:06

    following code worked for me

    @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
    
    0 讨论(0)
  • 2021-02-03 22:06

    I used this code (https://github.com/yoeriboven/TopAlignedCollectionViewLayout) after DongXu's solution didn't quite work. The only modification was that it's originally designed to be used with a grid so I needed to instantiate the layout with an arbitrarily high column count...

    let collectionViewFlowLayout = YBTopAlignedCollectionViewFlowLayout(numColumns: 1000)
    
    0 讨论(0)
  • 2021-02-03 22:07

    @DongXu: Your solution worked for me too. Here is the SWIFT version if it:

    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)
                }
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题