UICollection View Flow Layout Vertical Align
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 }}
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
@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) } } }}