I have a horizontal UICollectionView. I want to make the cells overlap each other by a certain amount of 60 pixels so that the second cells overlaps the first by 60 pixels a
Based on Maik Fruhner's answer. To use:
let someCollectionView = UICollectionView(frame: <frame you want>,
collectionViewLayout: CollectionViewOverlappingLayout())
Code for the custom layout:
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)
}
}
Works with swift 5 (and probably 4.x).
I used both answers from above to create an example for 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;
}
}
I used mdewitt's answer, converted it to swift 3 and i works great! For anyone interested:
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)
}
}
This doesn't blow up using UICollectionViewDiffableDataSource when calling numberOfItemsInSection)
(N.B. the other solutions fix the collectionViewContentSize / I welcome an edit to fix this without calling 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
}
}
Starting with this tutorial: https://web-beta.archive.org/web/20151116053251/http://blog.tobiaswiedenmann.com/post/35135290759/stacklayout
I modified the stacklayout specifically for a horizontal UICollectionView
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