2013-05-30 112 views
15

默认情况下,当您在集合视图中使用流布局时,单元是垂直居中。有没有办法改变这种对齐方式?UICollection视图流布局垂直对齐

enter image description here

+0

什么对准/设计? –

+0

底部对齐(沿着红线)。但底部或顶部,这并不重要,实现它的方式应该是一样的,对吗? –

回答

23

下面的代码为我工作

@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

测试了一堆解决方案,这是唯一按预期工作的解决方案。 – Johannes

+1

由于某些原因,这有时会起作用,但随后我会滚动并将先前顶部对齐的单元格居中。很奇怪。 – rob5408

+1

是的,它似乎工作,但有点怪异。它似乎不适用于iOS 9.任何建议,帮助。谢谢 – Frade

0

UICollectionViewFlowLayout类是从UICollectionViewLayout基类派生的。如果你看看documentation for that,你会发现有很多方法可以覆盖,最有可能的选择是layoutAttributesForItemAtIndexPath:

如果您重写该方法,则可以让它调用其超级实现,然后调整返回的对象的属性。具体而言,您可能需要调整frame属性以重新定位该项目,以便它不再居中。

+0

谢谢,我会尝试使用此解决方案。 –

3

这可能会或可能不适合你的特殊情况下工作,但我有一些运气下列方式继承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 
15

@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) 
      } 
     } 
    } 
} 
+0

你可以像下面那样去掉for循环中的'if'子句:for元素中的attrs where元素。representElementCategory == .cell {。 。 。 } – kikettas

0

我后东旭的解决方案也不太工作中使用此代码(https://github.com/yoeriboven/TopAlignedCollectionViewLayout)。唯一的修改是,它最初被设计为与电网中使用,所以我需要一个任意高的列数来实例化布局...

let collectionViewFlowLayout = YBTopAlignedCollectionViewFlowLayout(numColumns: 1000) 
2

斯威夫特3版本,以防有人只是想复制粘贴&:

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) 
      } 
     } 
    } 
} 
0

@ DongXu's answer是正确的。但是,我建议在UICollectionViewFlowLayoutprepare()方法中进行这些计算。它会阻止对同一个单元的属性进行多次计算。而且,prepare()是管理属性缓存的好地方。

2

我已经使用了类似以前的答案。 在我的情况下,我想按不同高度的柱子对齐单元格。

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 
    } 
} 

这是此布局的结果是:你想

enter image description here