在 Objective-C 中:
@interface MyFlowLayoutObjC()
@property (nonatomic, strong) NSMutableDictionary *cellsLayout;
@property (nonatomic, assign) CGSize computedContentSize;
@end
@implementation MyFlowLayoutObjC
-(id)init {
self = [super init];
if (self) {
_cellsLayout = [[NSMutableDictionary alloc] init];
}
return self;
}
-(void)prepareLayout {
[super prepareLayout];
[self setComputedContentSize:CGSizeZero];
[[self cellsLayout] removeAllObjects];
CGSize fullFrameSize = [[self collectionView] frame].size;
NSUInteger numberOfitems = [[self collectionView] numberOfItemsInSection:0];
CGSize unitSize = CGSizeMake(fullFrameSize.width / 3.0, fullFrameSize.width / 3.0);
for (NSUInteger anIndex = 0; anIndex < numberOfitems; anIndex ++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:anIndex inSection:0];
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
switch (anIndex) {
case 0:
[attributes setFrame:CGRectMake(0, 0, unitSize.width * 2, unitSize.height * 2)];
break;
case 1:
[attributes setFrame:CGRectMake(unitSize.width * 2, 0, unitSize.width, unitSize.height)];
break;
case 2:
[attributes setFrame:CGRectMake(unitSize.width * 2, unitSize.height, unitSize.width, unitSize.height)];
break;
default:
[attributes setFrame:CGRectMake(unitSize.width * (anIndex % 3), unitSize.height * (CGFloat)((anIndex / 3) + 1), unitSize.width, unitSize.height)];
break;
}
[[self cellsLayout] setObject:attributes forKey:indexPath];
[self setComputedContentSize:CGSizeMake(MAX([attributes frame].origin.x + [attributes frame].size.width, [self computedContentSize].width),
MAX([attributes frame].origin.y + [attributes frame].size.height, [self computedContentSize].height))];
}
}
-(NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *layouts = [[self cellsLayout] allValues];
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(UICollectionViewLayoutAttributes * _Nullable aLayout, NSDictionary<NSString *,id> * _Nullable bindings) {
return CGRectIntersectsRect([aLayout frame], rect);
}];
return [layouts filteredArrayUsingPredicate:predicate];
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
return [self cellsLayout][indexPath];
}
- (CGSize)collectionViewContentSize {
return [self computedContentSize];
}
@end
在 Swift 中:
class MyFlowLayout: UICollectionViewLayout {
private var cellsLayout: [IndexPath: UICollectionViewLayoutAttributes] = [:]
override func prepare() {
cellsLayout.removeAll()
guard let fullFrameSizeWidth = collectionView?.frame.size else { return }
guard let numberOfItems = collectionView?.numberOfItems(inSection: 0) else { return }
let unitSize = CGSize(width: fullFrameSizeWidth.width / 3.0, height: fullFrameSizeWidth.width / 3.0)
for anIndex in 0..<numberOfItems {
let indexPath = IndexPath(item: anIndex, section: 0)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
switch anIndex {
case 0:
attributes.frame = CGRect(origin: .zero, size: CGSize(width: unitSize.width * 2, height: unitSize.height * 2))
case 1:
attributes.frame = CGRect(origin: CGPoint(x: unitSize.width * 2, y: 0), size: unitSize)
case 2:
attributes.frame = CGRect(origin: CGPoint(x: unitSize.width * 2, y: unitSize.height), size: unitSize)
default:
attributes.frame = CGRect(origin: CGPoint(x: unitSize.width * CGFloat(anIndex % 3),
y: unitSize.height * CGFloat(Int(anIndex / 3) + 1)),
size: unitSize)
}
cellsLayout[indexPath] = attributes
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return cellsLayout.compactMap { $0.value.frame.intersects(rect) ? $0.value : nil }
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cellsLayout[indexPath]
}
override var collectionViewContentSize: CGSize {
return CGSize(width: cellsLayout.max(by: { $0.1.frame.maxX < $1.1.frame.maxX })?.value.frame.maxX ?? 0,
height: cellsLayout.max(by: { $0.1.frame.maxY < $1.1.frame.maxY })?.value.frame.maxY ?? 0)
}
}
这应该可以解决问题。
现在,让我们解释一下,您需要一个自定义的UICollectionViewFlowLayout。因为仅仅覆盖-collectionView:layout:sizeForItemAtIndexPath:,你不能这样做。它将获得第一个项目的大小,将其放入框架中。然后,获取第二个项目的框架,如果合适,则将其放在其右侧,否则放在其下方,依此类推。
设置好所有内容后,它将以每条线的 y 轴为中心。这就是你有这种行为的原因。
那么需要什么?
让我们覆盖-layoutAttributesForElementsInRect: 以返回与矩形(可见矩形)相交的每个单元格的布局。
为了做到这一点,我们需要知道这些框架,所以我在-prepareLayout进行了所需的计算。