UICollection View Flow Layout Vertical Align

后端 未结 10 2137
無奈伤痛
無奈伤痛 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 22:07

    Swift 3 Version in case someone just wants to Copy & Paste:

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

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

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

    I have used something similar to the before answers. In my case I want to align cells by colum with different heights.

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

    This is the result of this layout:

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

    The UICollectionViewFlowLayout class is derived from the UICollectionViewLayout base class. And if you look at the documentation for that, you'll see there are a number of methods you can override, the most likely candidate being layoutAttributesForItemAtIndexPath:.

    If you override that method, you could let it call its super implementation, and then adjust the properties of the returned UICollectionViewLayoutAttributes object. Specifically, you'll likely need to adjust the frame property to reposition the item so it's no longer centered.

    0 讨论(0)
提交回复
热议问题