默认情况下,当您在集合视图中使用流布局时,单元格垂直居中。有没有办法改变这种对齐方式?
11 回答
具有面向功能的方法的 Swift 4:
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
}
}
以下代码对我有用
@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:您的解决方案也对我有用。这是 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)
}
}
}
}
Swift 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)
}
}
}
}
我使用了类似于之前答案的东西。就我而言,我想按具有不同高度的列对齐单元格。
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
}
}
这是此布局的结果:
这可能适用于您的特定情况,也可能不适用于您的特定情况,但我有一些运气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
@DongXu:您的解决方案也对我有用。这是Xamarin.iOS版本,如果它:
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;
}
}
}
该类UICollectionViewFlowLayout
是从UICollectionViewLayout
基类派生的。如果您查看文档,您会发现有许多方法可以覆盖,最有可能的候选方法是layoutAttributesForItemAtIndexPath:
.
如果你重写那个方法,你可以让它调用它的超级实现,然后调整返回UICollectionViewLayoutAttributes
对象的属性。具体来说,您可能需要调整frame
属性以重新定位项目,使其不再居中。
我使用了 https://cocoapods.org/pods/AlignedCollectionViewFlowLayout
安装 pod 或简单地将文件 AlignedCollectionViewFlowLayout.swift 添加到您的项目中
在情节提要中,选择集合视图的“集合布局”并分配类 AlignedCollectionViewFlowLayout
- 在 View Controller 的 ViewDidLoad() 函数中添加:
let alignedFlowLayout = collectionView?.collectionViewLayout as? AlignedCollectionViewFlowLayout
alignedFlowLayout?.horizontalAlignment = .left
alignedFlowLayout?.verticalAlignment = .top
在 DongXu 的解决方案不太奏效后,我使用了这段代码(https://github.com/yoeriboven/TopAlignedCollectionViewLayout )。唯一的修改是它最初设计用于网格,所以我需要用任意高的列数来实例化布局......
let collectionViewFlowLayout = YBTopAlignedCollectionViewFlowLayout(numColumns: 1000)
@DongXu 的回答是正确的。但是,我建议在UICollectionViewFlowLayout
'sprepare()
方法中进行这些计算。它将防止对同一单元格的属性进行多次计算。此外,prepare()
是管理属性缓存的更好地方。