2013-05-30 8 views
15

Domyślnie, gdy korzystasz z układu przepływu w widoku kolekcji, komórki są centrami w pionie. Czy istnieje sposób na zmianę tego dostosowania?Widok UICollection Układ przepływu Wyrównanie w pionie

enter image description here

+0

co wyrównanie/konstrukcja chcesz ?? –

+0

Wyrównanie dolne (wzdłuż czerwonej linii). Ale na dole lub na górze, to nie jest ważne, sposób na zrobienie tego powinien być taki sam, prawda? –

Odpowiedz

23

następujący kod pracował dla mnie

@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

Testował szereg rozwiązań, to był jedyny działający zgodnie z oczekiwaniami. – Johannes

+1

Z jakiegoś powodu czasem to by się zdarzyło, ale wtedy przewijałem i komórki uprzednio wyrównane byłyby wyśrodkowane. Bardzo dziwny. – rob5408

+1

Tak, wydaje się działać, ale trochę dziwne. Wydaje się nie działać na iOS 9. Wszelkie sugestie, pomoc. dzięki – Frade

0

Klasa UICollectionViewFlowLayout pochodzi z klasy UICollectionViewLayout bazowej. A jeśli spojrzysz na documentation for that, zobaczysz, że istnieje wiele metod, które możesz zastąpić, a najbardziej prawdopodobnym kandydatem jest layoutAttributesForItemAtIndexPath:.

Jeśli zastąpisz tę metodę, możesz pozwolić jej wywołać jej super implementację, a następnie dostosować właściwości zwróconego obiektu UICollectionViewLayoutAttributes. W szczególności będziesz musiał dostosować właściwość frame, aby zmienić położenie elementu, aby nie był już wycentrowany.

+0

Dzięki, spróbuję z tym rozwiązaniem. –

3

To może lub nie może pracować dla danej sytuacji, ale miałem trochę szczęścia instacji UICollectionViewFlowLayout w następujący sposób:

@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: Twój rozwiązanie zadziałało również dla mnie. Oto wersja SWIFT jeżeli:

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

Możesz pozbyć się klauzuli 'if' wewnątrz pętli for w następujący sposób: dla elementu w attrs gdzie element.representElementCategory == .cell {. . . } – kikettas

0

Użyłem tego kodu (https://github.com/yoeriboven/TopAlignedCollectionViewLayout) po rozwiązanie Dongxu za nie do końca pracy. Jedyną modyfikacją było, że to pierwotnie zaprojektowane do użytku z siatką, więc potrzebne do wystąpienia układ o dowolnie dużej liczbie kolumny ...

let collectionViewFlowLayout = YBTopAlignedCollectionViewFlowLayout(numColumns: 1000) 
2

Swift 3 Wersja w przypadku, gdy ktoś po prostu chce skopiować & Wklej:

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 jest poprawne. Sugeruję jednak wykonanie tych obliczeń w metodzie UICollectionViewFlowLayout. Zapobiegnie to wielokrotnym obliczeniom na tych samych komórkach. Ponadto, prepare() jest lepszym miejscem do zarządzania pamięcią podręczną atrybutów.

2

Użyłem czegoś podobnego do poprzednich odpowiedzi. W moim przypadku chcę wyrównać komórki przez kolumnę o różnych wysokościach.

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

Wynika to z tego układu:

enter image description here

Powiązane problemy