UICollectionView 单元格重叠
Posted
技术标签:
【中文标题】UICollectionView 单元格重叠【英文标题】:UICollectionView Cell Overlap 【发布时间】:2013-04-10 16:21:04 【问题描述】:我有一个水平的 UICollectionView。我想让单元格相互重叠一定数量的 60 像素,以便第二个单元格与第一个单元格重叠 60 个像素,第三个单元格与第二个单元格重叠相同的数量,依此类推。
我尝试对 UICollectionViewFlowLayout 进行子类化。但我无法将 UICollectionviewCell 放在另一个之上。
【问题讨论】:
【参考方案1】:从本教程开始:https://web-beta.archive.org/web/20151116053251/http://blog.tobiaswiedenmann.com/post/35135290759/stacklayout
我专门为水平 UICollectionView 修改了 stacklayout
UICollectionViewStackLayout.h
#import <UIKit/UIKit.h>
@interface UICollectionViewStackLayout : UICollectionViewFlowLayout
#define STACK_OVERLAP 60 //this corresponds to 60 points which is probably what you want, not pixels
#define ITEM_SIZE CGSizeMake(190,210)
@end
UICollectionViewStackLayout.m
#import "UICollectionViewStackLayout.h"
@implementation UICollectionViewStackLayout
-(id)init
self = [super init];
if (self)
[self commonInit];
return self;
-(id)initWithCoder:(NSCoder *)aDecoder
if (self = [super init])
[self commonInit];
return self;
-(void)commonInit
self.itemSize = ITEM_SIZE;
//set minimum layout requirements
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.minimumInteritemSpacing = 0;
-(CGSize)collectionViewContentSize
NSInteger xSize = [self.collectionView numberOfItemsInSection:0]
* self.itemSize.width;
NSInteger ySize = [self.collectionView numberOfSections]
* (self.itemSize.height);
CGSize contentSize = CGSizeMake(xSize, ySize);
if (self.collectionView.bounds.size.width > contentSize.width)
contentSize.width = self.collectionView.bounds.size.width;
if (self.collectionView.bounds.size.height > contentSize.height)
contentSize.height = self.collectionView.bounds.size.height;
return contentSize;
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
NSArray* attributesArray = [super layoutAttributesForElementsInRect:rect];
int numberOfItems = [self.collectionView numberOfItemsInSection:0];
for (UICollectionViewLayoutAttributes *attributes in attributesArray)
CGFloat xPosition = attributes.center.x;
CGFloat yPosition = attributes.center.y;
if (attributes.indexPath.row == 0)
attributes.zIndex = INT_MAX; // Put the first cell on top of the stack
else
xPosition -= STACK_OVERLAP * attributes.indexPath.row;
attributes.zIndex = numberOfItems - attributes.indexPath.row; //Other cells below the first one
attributes.center = CGPointMake(xPosition, yPosition);
return attributesArray;
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path
UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];
return attributes;
@end
【讨论】:
我在 Apple tv 应用中实现了您的答案。但我面临的问题是第一个单元格是完美的,但是当我移动焦点下一个单元格时其他所有单元格重叠,如果我移动焦点结果应该与第一个单元格相同,请给我一个完美的解决方案。【参考方案2】:我使用了 mdewitt 的答案,将其转换为 swift 3,效果很好! 对于任何有兴趣的人:
class CollectionViewOverlappingLayout: UICollectionViewFlowLayout
var overlap: CGFloat = 30
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
self.scrollDirection = .horizontal
self.minimumInteritemSpacing = 0
override var collectionViewContentSize: CGSize
let xSize = CGFloat(self.collectionView!.numberOfItems(inSection: 0)) * self.itemSize.width
let ySize = CGFloat(self.collectionView!.numberOfSections) * self.itemSize.height
var contentSize = CGSize(width: xSize, height: ySize)
if self.collectionView!.bounds.size.width > contentSize.width
contentSize.width = self.collectionView!.bounds.size.width
if self.collectionView!.bounds.size.height > contentSize.height
contentSize.height = self.collectionView!.bounds.size.height
return contentSize
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
let attributesArray = super.layoutAttributesForElements(in: rect)
let numberOfItems = self.collectionView!.numberOfItems(inSection: 0)
for attributes in attributesArray!
var xPosition = attributes.center.x
let yPosition = attributes.center.y
if attributes.indexPath.row == 0
attributes.zIndex = Int(INT_MAX) // Put the first cell on top of the stack
else
xPosition -= self.overlap * CGFloat(attributes.indexPath.row)
attributes.zIndex = numberOfItems - attributes.indexPath.row //Other cells below the first one
attributes.center = CGPoint(x: xPosition, y: yPosition)
return attributesArray
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
return UICollectionViewLayoutAttributes(forCellWith: indexPath)
【讨论】:
滚动时的错误。集合末尾的空白单元格和一大片空白区域。【参考方案3】:基于 Maik Fruhner 的回答。使用方法:
let someCollectionView = UICollectionView(frame: <frame you want>,
collectionViewLayout: CollectionViewOverlappingLayout())
自定义布局代码:
class CollectionViewOverlappingLayout: UICollectionViewFlowLayout
var overlap: CGFloat = 14
override init()
super.init()
self.sharedInit()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
self.sharedInit()
func sharedInit()
self.scrollDirection = .horizontal
self.minimumInteritemSpacing = 0
override var collectionViewContentSize: CGSize
let xSize = CGFloat(self.collectionView!.numberOfItems(inSection: 0)) * self.itemSize.width
let ySize = CGFloat(self.collectionView!.numberOfSections) * self.itemSize.height
var contentSize = CGSize(width: xSize, height: ySize)
if self.collectionView!.bounds.size.width > contentSize.width
contentSize.width = self.collectionView!.bounds.size.width
if self.collectionView!.bounds.size.height > contentSize.height
contentSize.height = self.collectionView!.bounds.size.height
return contentSize
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
let attributesArray = super.layoutAttributesForElements(in: rect)
let numberOfItems = self.collectionView!.numberOfItems(inSection: 0)
for attributes in attributesArray!
var xPosition = attributes.center.x
let yPosition = attributes.center.y
if attributes.indexPath.row == 0
attributes.zIndex = Int(INT_MAX) // Put the first cell on top of the stack
else
xPosition -= self.overlap * CGFloat(attributes.indexPath.row)
attributes.zIndex = numberOfItems - attributes.indexPath.row //Other cells below the first one
attributes.center = CGPoint(x: xPosition, y: yPosition)
return attributesArray
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
return UICollectionViewLayoutAttributes(forCellWith: indexPath)
适用于 swift 5(可能还有 4.x)。
【讨论】:
它与 diffable 数据源一起爆炸 - 它似乎想在 dataSource.apply 上使用 self.collectionView!.numberOfItems【参考方案4】:我使用上面的两个答案为 Xamarin.ios 创建了一个示例:
public class CustomFlowLayout : UICollectionViewFlowLayout
public CustomFlowLayout(nfloat width, nfloat height)
ItemSize = new CGSize(width, height);
ScrollDirection = UICollectionViewScrollDirection.Horizontal;
public override CGSize CollectionViewContentSize
get
var xSize = CollectionView.NumberOfItemsInSection(0) * ItemSize.Width;
var ySize = CollectionView.NumberOfSections() * ItemSize.Height;
var contentSize = new CGSize(xSize, ySize);
if (CollectionView.Bounds.Size.Width > contentSize.Width)
contentSize.Width = CollectionView.Bounds.Size.Width;
if (CollectionView.Bounds.Size.Height > contentSize.Height)
contentSize.Height = CollectionView.Bounds.Size.Height;
return contentSize;
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(CGRect rect)
var attributesArray = base.LayoutAttributesForElementsInRect(rect);
var numberOfItems = CollectionView.NumberOfItemsInSection(0);
foreach (var attributes in attributesArray)
var xPosition = attributes.Center.X;
var yPosition = attributes.Center.Y;
if (attributes.IndexPath.Row == 0)
attributes.ZIndex = int.MaxValue; // Put the first cell on top of the stack
else
xPosition -= 20 * attributes.IndexPath.Row; //20 - value based on your cell width
attributes.ZIndex = numberOfItems - attributes.IndexPath.Row; //Other cells below the first one
attributes.Center = new CGPoint(xPosition, yPosition);
return attributesArray;
【讨论】:
你在效果器中使用过这个吗?你也可以分享那个代码吗? 现在不在我的笔记本电脑附近,但如果我没记错的话,它看起来像 uicollectionview colview = new uicollectionview(); colview.ViewFlowLayout = new CustomFlowLayout(number, number) 其中数字通常与屏幕宽度和高度成正比。它对你有帮助吗?明天我可以发布实际代码。你遇到了什么问题? 那太好了。当我使用此代码为UICollectionView.SetCollectionViewLayout(UICollectionViewLayout, bool)
方法创建实例时,我的应用程序会静默崩溃。
显然只有你可以编辑它,所以请这样做 - 这样其他人就不必像我一样想出同样的事情了。
@iSpain17 感谢您的提醒。我在实际实现中已将其删除,但忘记在此处更新。【参考方案5】:
在调用 numberOfItemsInSection 时使用 UICollectionViewDiffableDataSource 不会崩溃)
(注意,其他解决方案修复了 collectionViewContentSize / 我欢迎编辑以解决此问题,而无需调用 numberOfItemsInSection。
var cellLayout = OverlapLayout()
cellLayout.scrollDirection = .horizontal
cellLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
cellLayout.itemSize = CGSize(width: 44, height: 44)
class OverlapLayout: UICollectionViewFlowLayout
var overlap: CGFloat = 20
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
let arr = super.layoutAttributesForElements(in: rect)!
return arr.map atts in
var atts = atts
if atts.representedElementCategory == .cell
let ip = atts.indexPath
atts = self.layoutAttributesForItem(at: ip)!
return atts
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
let atts = super.layoutAttributesForItem(at: indexPath)!
var xPosition = atts.center.x
if indexPath.item == 0
return atts
else
xPosition -= self.overlap * CGFloat(atts.indexPath.row)
atts.center = CGPoint(x: xPosition, y: atts.center.y)
return atts
// Requires Snapkit
class BubbleCell: UICollectionViewCell
static let ID = "BubbleCell"
var magicCornerRadius = 22 // make this half the collectionview height
lazy var bubbleImageView: UIImageView = UIImageView(frame: .zero)
override init(frame: CGRect)
super.init(frame: frame)
setupViews()
required init?(coder: NSCoder)
fatalError("init(coder:) has not been implemented")
private func setupViews()
self.backgroundColor = .clear
contentView.backgroundColor = .clear
// Hero Image
contentView.addSubview(bubbleImageView)
bubbleImageView.snp.remakeConstraints (make) in
make.edges.equalToSuperview()
bubbleImageView.layer.cornerRadius = magicCornerRadius
bubbleImageView.layer.borderColor = UIColor.white.cgColor
bubbleImageView.layer.borderWidth = 2
bubbleImageView.clipsToBounds = true
bubbleImageView.image = kPlaceholderImage
bubbleImageView.contentMode = .scaleAspectFill
func configureCell(imageURL: URL)
// use third party library to fetch image here / that caches eg. SDWebImage
【讨论】:
我相信这个解决方案还没有完成。如果您有许多项目 (>100) 并前后滚动,重叠将改变其方向并有视觉错误。另外,滚动距离会比内容大很多。 根据 cmets / 这是一个 hack - 需要更多的工作。对不起。以上是关于UICollectionView 单元格重叠的主要内容,如果未能解决你的问题,请参考以下文章
当我使用按钮增加单元格高度时,UICollectionView 单元格相互重叠
UICollectionView 单元格与 Header 重叠