215

How do I set cell spacing in a section of UICollectionView? I know there is a property minimumInteritemSpacing I have set it to 5.0 still the spacing is not appearing 5.0. I have implemented the flowout delegate method.

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{

    return 5.0;
}

still I am not getting the desired result. I think its the minimum spacing . Isn't there any way by which I can set the maximum spacing?

simulator sc

Bhavin Ramani
  • 3,221
  • 5
  • 30
  • 41
Vishal Singh
  • 4,400
  • 4
  • 27
  • 43
  • did u check that whether it is entering the delegate method or not..? by putting the breakpoint.. – Prashant Nikam Jun 21 '13 at 07:01
  • yes it is entering the delegate, in IB also i have set the min cell spacing to 5. But i dont think minimum space is the property which will help me as it will just make sure that the minimum space between cells should be 5, but if collection view has extra space , than it will use that extra space. There should be something like maximum cell spacing – Vishal Singh Jun 21 '13 at 07:04
  • I have the same problem. 5px seems not to work. Intresstingly I can do this with a UITableView, but not with a UICollectionView... – jerik Jul 09 '13 at 17:07
  • I have fixed it doing some calculations..i will post it tommorow.:) – Vishal Singh Jul 09 '13 at 20:14
  • ***NOTE FOR 2016*** it's this easy: http://stackoverflow.com/a/28327193/294884 – Fattie Jun 25 '16 at 20:42
  • `minimumLineSpacing` is for space between lines/rows, while `minimumInteritemSpacing` is for space between columns. They have default value of 10. – Zhou Haibo Sep 09 '21 at 10:00

26 Answers26

198

Supporting the initial question. I tried to get the spacing to 5px on the UICollectionView but this does not work, as well with a UIEdgeInsetsMake(0,0,0,0)...

On a UITableView I can do this by directly specifying the x,y coordinates in a row...

enter image description here

Heres my UICollectionView code:

#pragma mark collection view cell layout / size
- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return [self getCellSize:indexPath];  // will be w120xh100 or w190x100
    // if the width is higher, only one image will be shown in a line
}

#pragma mark collection view cell paddings
- (UIEdgeInsets)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    return UIEdgeInsetsMake(0, 0, 0, 0); // top, left, bottom, right
}

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {

    return 5.0;
}

Update: Solved my problem, with the following code.

enter image description here

ViewController.m

#import "ViewController.h"
#import "MagazineCell.h" // created just the default class. 

static NSString * const cellID = @"cellID";

@interface ViewController ()

@end

@implementation ViewController

#pragma mark - Collection view
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 30;
}

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    MagazineCell *mCell = (MagazineCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];

    mCell.backgroundColor = [UIColor lightGrayColor];

    return mCell;
}

#pragma mark Collection view layout things
// Layout: Set cell size
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {

    NSLog(@"SETTING SIZE FOR ITEM AT INDEX %d", indexPath.row);
    CGSize mElementSize = CGSizeMake(104, 104);
    return mElementSize;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
    return 2.0;
}

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
    return 2.0;
}

// Layout: Set Edges
- (UIEdgeInsets)collectionView:
(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
   // return UIEdgeInsetsMake(0,8,0,8);  // top, left, bottom, right
    return UIEdgeInsetsMake(0,0,0,0);  // top, left, bottom, right
}

@end
Cœur
  • 37,241
  • 25
  • 195
  • 267
jerik
  • 5,714
  • 8
  • 41
  • 80
  • This is a much better answer. Take a look at the callbacks you can adjust in the UICollectionViewDelegateFlowLayout and you can see how this can be tweaked. – cynistersix Dec 17 '14 at 02:52
  • 1
    I thins this can only work when the width of item + spacing is equals with the width of uicollectionview. – xi.lin Aug 27 '15 at 08:55
  • @JayprakashDubey currently i am not fit in swift. You have to try it. Good luck – jerik Aug 12 '16 at 06:54
  • Where are you specifying the width between the cells of 5px? – UKDataGeek Apr 01 '17 at 12:04
  • @MobileBloke It's a long time since I wrote this code. Currently I have no glue, I think I reduced the width between the cells to 4px or 2px. – jerik Apr 03 '17 at 15:47
  • 1
    Much better answer than the accepted one, mainly because subclassing UICollectionViewLayout just to override one method seems like overkill, and is no trivial matter given all the other methods that need to be overridden. – Erika Electra Jul 14 '17 at 11:50
152

I know that the topic is old, but in case anyone still needs correct answer here what you need:

  1. Override standard flow layout.
  2. Add implementation like that:

    - (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect {
        NSArray *answer = [super layoutAttributesForElementsInRect:rect];
    
        for(int i = 1; i < [answer count]; ++i) {
            UICollectionViewLayoutAttributes *currentLayoutAttributes = answer[i];
            UICollectionViewLayoutAttributes *prevLayoutAttributes = answer[i - 1];
            NSInteger maximumSpacing = 4;
            NSInteger origin = CGRectGetMaxX(prevLayoutAttributes.frame);
    
            if(origin + maximumSpacing + currentLayoutAttributes.frame.size.width < self.collectionViewContentSize.width) {
                CGRect frame = currentLayoutAttributes.frame;
                frame.origin.x = origin + maximumSpacing;
                currentLayoutAttributes.frame = frame;
            }
        }
        return answer;
    }
    

where maximumSpacing could be set to any value you prefer. This trick guarantees that the space between cells would be EXACTLY equal to maximumSpacing!!

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
iago849
  • 1,818
  • 1
  • 13
  • 9
  • So, where do we put this? – Jonny Feb 14 '14 at 09:54
  • 1
    Hey Jonny, inherit from standard Flow Layout and just copy and paste this method into your class. – iago849 Apr 02 '14 at 08:48
  • 1
    Note that you don't need to make a `mutableCopy`: you're not mutating the contents of the array, but mutating the objects within it. – Brock Boland Dec 29 '14 at 16:34
  • 5
    maybe I do something wrong or this idea is not working for 100%, because when I scroll to the end I see that some cells are overlay each other :( – pash3r Mar 05 '16 at 10:19
  • Flow layout as specified in answer – iago849 Mar 22 '16 at 10:59
  • 2
    Great post. I did encounter an error with it upsetting the formatting of the last line of the layout in some circumstances. To resolve I changed the `if` to AND an additional condition: `if(origin + maximumSpacing + currentLayoutAttributes.frame.size.width < self.collectionViewContentSize.width && currentLayoutAttributes.frame.origin.x > prevLayoutAttributes.frame.origin.x) {` – chrisoneiota Sep 09 '16 at 09:32
  • Great post i am getting extra space as @chrisoneiota mentioned and i sloved using his code. :-) – Ashok R Nov 06 '16 at 15:37
  • does anyone have a swift 3 solution to this? Im finding it hard to create an extension and override this properly – Jeremy Sh Dec 15 '16 at 14:08
  • Works just like a charm. But can you deeply explain how does it work ? – Karaban Feb 24 '17 at 12:35
  • layoutAttributesForElementsInRect is just a overriden method of Flow Layout. I simply move X points of each default frame (you see call super) so that frames are 4 pixels between each other – iago849 Mar 14 '17 at 13:04
  • This answer is not MAXIMUM spacing. Just set to *maximumSpacing* variable value.
    If want to set to the real maximum spacing, need to additional condition: `if(originX + self.maximumInteritemSpacing + CGRectGetWidth(currentLayoutAttributes.frame) < self.collectionViewContentSize.width && CGRectGetMinX(currentLayoutAttributes.frame) - originX > self.maximumInteritemSpacing) {`
    – strawnut Jun 11 '17 at 07:37
84

Using a horizontal flow layout, I was also getting a 10 points spacing between cells. To remove the spacing I needed to set minimumLineSpacing as well as minimumInterItemSpacing to zero.

UICollectionViewFlowLayout *flow = [[UICollectionViewFlowLayout alloc] init];
flow.itemSize = CGSizeMake(cellWidth, cellHeight);
flow.scrollDirection = UICollectionViewScrollDirectionHorizontal;
flow.minimumInteritemSpacing = 0;
flow.minimumLineSpacing = 0;

Also, if all your cells are the same size, it's simpler and more efficient to set the property on the flow layout directly instead of using delegate methods.

Matt H
  • 6,422
  • 2
  • 28
  • 32
Jason Moore
  • 7,169
  • 1
  • 44
  • 45
  • 45
    Surprisingly, minimumLineSpacing affects the horizontal space between cells. – Matt H Sep 23 '14 at 19:13
  • 4
    ME too, I need a horizontal scroll and zero spacing between cells, and minimumLineSpacing = 0 is the key. – Wingzero Feb 03 '15 at 07:35
  • 4
    I was able to get this effect _without_ using `minimumInteritemSpacing`. Just setting `minimumLineSpacing = 0` on my horizontal layout removed the horizontal spacing between items. – Ziewvater Oct 16 '15 at 21:26
  • 1
    If you think about it, the horizontal distance between items IS the line spacing for a horizontally flowing layout. The line spacing is the vertical distance between items in a typical, vertically flowing layout. – lancejabr Feb 24 '17 at 21:19
80

Remember, it is minimum line space, not minimum inter item spacing or cell space. Because your collectionView's scroll direction is HORIZONTAL.

If it is vertical then you need to set cell space or inter item space for horizontal space between cells and line spacing for vertical space between cells.

Objective-C version

- (CGFloat)collectionView:(UICollectionView *)collectionView
                       layout:(UICollectionViewLayout*)collectionViewLayout
    minimumLineSpacingForSectionAtIndex:(NSInteger)section
    {
        return 20;
    }

Swift version:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    return 20
}
Vincent Joy
  • 2,598
  • 15
  • 25
  • now bottom space is gone when i return 0; how to remove right side space ? – Bangalore Dec 15 '15 at 06:41
  • Hey, I didn't understand your question very well. But I think **insetForSectionAtIndex** is what you are looking for. - (UIEdgeInsets)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { return UIEdgeInsetsMake(5, 10, 5, 10); } – Vincent Joy Dec 15 '15 at 07:13
  • It is minimum line spacing for both horizontal and vertical. – Swati Jun 29 '18 at 12:27
  • worked for me for vertical space between collectionview cell. – Ravi Feb 26 '20 at 08:04
37

Try this code to ensure you have a spacing of 5px between each item:

- (UIEdgeInsets)collectionView:(UICollectionView *) collectionView 
                        layout:(UICollectionViewLayout *) collectionViewLayout 
        insetForSectionAtIndex:(NSInteger) section {

    return UIEdgeInsetsMake(0, 0, 0, 5); // top, left, bottom, right
}

- (CGFloat)collectionView:(UICollectionView *) collectionView
                   layout:(UICollectionViewLayout *) collectionViewLayout
  minimumInteritemSpacingForSectionAtIndex:(NSInteger) section {    
    return 5.0;
}
Community
  • 1
  • 1
LASoft
  • 371
  • 3
  • 2
35

Swift version of the most popular answer. Space between the cells will be equal to cellSpacing.

class CustomViewFlowLayout : UICollectionViewFlowLayout {

    let cellSpacing:CGFloat = 4

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        if let attributes = super.layoutAttributesForElementsInRect(rect) {
        for (index, attribute) in attributes.enumerate() {
                if index == 0 { continue }
                let prevLayoutAttributes = attributes[index - 1]
                let origin = CGRectGetMaxX(prevLayoutAttributes.frame)
                if(origin + cellSpacing + attribute.frame.size.width < self.collectionViewContentSize().width) {
                    attribute.frame.origin.x = origin + cellSpacing
                }
            }
            return attributes
        }
        return nil
    }
}
samwize
  • 25,675
  • 15
  • 141
  • 186
Vojtech Vrbka
  • 5,342
  • 6
  • 44
  • 63
  • super.layoutAttributesForElements(in: rect) is returning nil any idea? – Ashok R Nov 06 '16 at 14:09
  • Like all the other methods that use this, this does not work properly for multiple rows. Intermittent, rounding-error-style gaps will remain between rows, even with the appropriate delegate methods and properties set to use 0. It may be a UICollectionView rendering issue. – Womble Jun 13 '18 at 01:19
32

I have found very easy way to configure spacing between cells or rows by using IB.

Just select UICollectionView from storyboard/Xib file and click in Size Inspector as specified in below image.

enter image description here

For configuring space programatically use following properties.

1) For setting space between rows.

[self.collectionView setMinimumLineSpacing:5];

2) For setting space between items/cells.

[self.collectionView setMinimumInteritemSpacing:5];
Aamir
  • 16,329
  • 10
  • 59
  • 65
22

Please note the property name minimumInterItemSpacing . This will be the minimum spacing between the items not the exact spacing. If you set minimumInterItemSpacing to some value you can assure that spacing wont be a value less than that. But there is a chance get a higher value.

Actually the spacing between items depends on several factors itemSize and sectionInset. Collection view dynamically place the contents based on these values. So you cannot assure the exact spacing. You should do some trial and error with sectionInset and minimumInterItemSpacing.

Anil Varghese
  • 42,757
  • 9
  • 93
  • 110
16

Answer for Swift 3.0, Xcode 8

1.Make sure you set collection view delegate

class DashboardViewController: UIViewController {
    @IBOutlet weak var dashboardCollectionView: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()
        dashboardCollectionView.delegate = self
    }
}

2.Implement UICollectionViewDelegateFlowLayout protocol, not UICollectionViewDelegate.

extension DashboardViewController: UICollectionViewDelegateFlowLayout {
    fileprivate var sectionInsets: UIEdgeInsets {
        return .zero
    }

    fileprivate var itemsPerRow: CGFloat {
        return 2
    }

    fileprivate var interitemSpace: CGFloat {
        return 5.0
    }

    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        let sectionPadding = sectionInsets.left * (itemsPerRow + 1)
        let interitemPadding = max(0.0, itemsPerRow - 1) * interitemSpace
        let availableWidth = collectionView.bounds.width - sectionPadding - interitemPadding
        let widthPerItem = availableWidth / itemsPerRow

        return CGSize(width: widthPerItem, height: widthPerItem)
    }

    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        insetForSectionAt section: Int) -> UIEdgeInsets {
        return sectionInsets
    }

    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 0.0
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return interitemSpace
    }
}
Vadim Bulavin
  • 3,697
  • 25
  • 19
  • 1
    Think you can remove the public there. Should work fine, and consistent with all other func(s). – Edward Potter Dec 12 '16 at 22:28
  • Would this work for vertical scroll collection view? I have collection view where it should show 2 cells horizontally on devices like iPhone 6s and 6s Plus, but should show just one cell for anything lower than that i.e. 5s,5 and SE. My current code is working but in 6s Plus device the gap between the cells is just too much and looks ugly and I am trying to lessen that gap and that's the only requirement since all other devices are working as per my design. – Anjan Biswas Jul 02 '17 at 05:26
13

Simple code for spacing

let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.minimumInteritemSpacing = 10 // Some float value
Yuchen
  • 30,852
  • 26
  • 164
  • 234
AMayes
  • 1,767
  • 1
  • 16
  • 29
10

The voted answer (and also the swift version) has a small issue: there will be a big spacing on the right.

This is because the flow layout is customised to make the cell spacing exact, with a float left behaviour.

My solution is to manipulate the section inset, so that the section is align center, yet the spacing is exactly as specified.

In screenshot below, the item/line spacing is exactly 8pt, while the section left & right inset will be bigger than 8pt (to make it center aligned):

evenly spaced cells

Swift code as such:

private let minItemSpacing: CGFloat = 8
private let itemWidth: CGFloat      = 100
private let headerHeight: CGFloat   = 32

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    // Create our custom flow layout that evenly space out the items, and have them in the center
    let layout = UICollectionViewFlowLayout()
    layout.itemSize = CGSize(width: itemWidth, height: itemWidth)
    layout.minimumInteritemSpacing = minItemSpacing
    layout.minimumLineSpacing = minItemSpacing
    layout.headerReferenceSize = CGSize(width: 0, height: headerHeight)

    // Find n, where n is the number of item that can fit into the collection view
    var n: CGFloat = 1
    let containerWidth = collectionView.bounds.width
    while true {
        let nextN = n + 1
        let totalWidth = (nextN*itemWidth) + (nextN-1)*minItemSpacing
        if totalWidth > containerWidth {
            break
        } else {
            n = nextN
        }
    }

    // Calculate the section inset for left and right. 
    // Setting this section inset will manipulate the items such that they will all be aligned horizontally center.
    let inset = max(minItemSpacing, floor( (containerWidth - (n*itemWidth) - (n-1)*minItemSpacing) / 2 ) )
    layout.sectionInset = UIEdgeInsets(top: minItemSpacing, left: inset, bottom: minItemSpacing, right: inset)

    collectionView.collectionViewLayout = layout
}
Community
  • 1
  • 1
samwize
  • 25,675
  • 15
  • 141
  • 186
  • 1
    I get a 'NSInvalidArgumentException', reason: '*** setObjectForKey: object cannot be nil (key: {length = 2, path = 0 - 0})' uncaught exception. – DaftWooly Jun 17 '16 at 10:50
  • ... if I'm not mistaken, your while loop is equivalent to : `let n = Int((containerWidth + minItemSpacing)/((itemWidth+minItemSpacing)))` – DaftWooly Jun 17 '16 at 10:52
  • Didn't used the code as is, but this gave me the best reference for implementing my out flow layout for the collection view. cheers – Dima Gimburg Apr 05 '17 at 21:35
9

Storyboard Approach

Select CollectionView in your storyboard and go to size inspector and set min spacing for cells and lines as 5

Swift 5 Programmatically

lazy var collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal

        //Provide Width and Height According to your need
        let width = UIScreen.main.bounds.width / 4
        let height = UIScreen.main.bounds.height / 10
        layout.itemSize = CGSize(width: width, height: height)

        //For Adjusting the cells spacing
        layout.minimumInteritemSpacing = 5
        layout.minimumLineSpacing = 5

        return UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
    }()
Shubham Mishra
  • 1,303
  • 13
  • 24
8

Define UICollectionViewDelegateFlowLayout protocol in your header file.

Implement following method of UICollectionViewDelegateFlowLayout protocol like this:

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
    return UIEdgeInsetsMake(5, 5, 5, 5);
}

Click Here to see Apple Documentation of UIEdgeInsetMake method.

Salman Zaidi
  • 9,342
  • 12
  • 44
  • 61
4

If u want to tweak the spacing without touching the actual cell size, this is the solution that worked best for me. #xcode 9 #tvOS11 #iOS11 #swift

So in UICollectionViewDelegateFlowLayout, change implement the next methods, the trick is u have to use both of them, and the documentation was not really pointing me to think in that direction. :D

open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
    return cellSpacing
}

public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    return cellSpacing
}
Boris
  • 178
  • 1
  • 7
3

Well, if you're creating a horizontal collection view then to give space between the cells, you need to set the property minimumLineSpacing .

Shanu Singh
  • 365
  • 3
  • 16
2

I'm using monotouch, so the names and code will be a bit different, but you can do this by making sure that the width of the collectionview equals (x * cell width) + (x-1) * MinimumSpacing with x = amount of cells per row.

Just do following steps based on your MinimumInteritemSpacing and the Width of the Cell

1) We calculate amount of items per row based on cell size + current insets + minimum spacing

float currentTotalWidth = CollectionView.Frame.Width - Layout.SectionInset.Left - Layout.SectionInset.Right (Layout = flowlayout)
int amountOfCellsPerRow = (currentTotalWidth + MinimumSpacing) / (cell width + MinimumSpacing)

2) Now you have all info to calculate the expected width for the collection view

float totalWidth =(amountOfCellsPerRow * cell width) + (amountOfCellsPerRow-1) * MinimumSpacing

3) So the difference between the current width and the expected width is

float difference = currentTotalWidth - totalWidth;

4) Now adjust the insets (in this example we add it to the right, so the left position of the collectionview stays the same

Layout.SectionInset.Right = Layout.SectionInset.Right + difference;
Matt
  • 1,111
  • 11
  • 20
2

I have a horizontal UICollectionView and subclassed UICollectionViewFlowLayout. The collection view has large cells, and only shows one row of them at a time, and the collection view fits the width of the screen.

I tried iago849's answer and it worked, but then I found out I didn't even need his answer. For some reason, setting the minimumInterItemSpacing does nothing. The spacing between my items/cells can be entirely controlled by minimumLineSpacing.

Not sure why it works this way, but it works.

user3344977
  • 3,584
  • 4
  • 32
  • 88
  • 1
    **this seems to be true** - oddly the "lines" value is, in fact, the separator between items in the case of a horizontal layout. – Fattie Jul 27 '19 at 20:43
  • Hey @user3344977 - nice find. I decided to separate this issue out into a separate question that directly addresses horizontal scroll collection views so that it will be easier to find for some people. I reference your answer there. My question is here: https://stackoverflow.com/q/61774415/826946 – Andy Weinstein May 13 '20 at 12:19
2

My solution in Swift 3 cell line spacing like in Instagram:

enter image description here

lazy var collectionView: UICollectionView = {
    let layout = UICollectionViewFlowLayout()
    let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
    cv.backgroundColor = UIColor.rgb(red: 227, green: 227, blue: 227)
    cv.showsVerticalScrollIndicator = false
    layout.scrollDirection = .vertical
    layout.minimumLineSpacing = 1
    layout.minimumInteritemSpacing = 1
    return cv
}()

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

    switch UIDevice.current.modelName {
    case "iPhone 4":
        return CGSize(width: 106, height: 106)
    case "iPhone 5":
        return CGSize(width: 106, height: 106)
    case "iPhone 6,7":
        return CGSize(width: 124, height: 124)
    case "iPhone Plus":
        return CGSize(width: 137, height: 137)
    default:
        return CGSize(width: frame.width / 3, height: frame.width / 3)
    }
}

How to detect device programmaticlly: https://stackoverflow.com/a/26962452/6013170

Janserik
  • 2,306
  • 1
  • 24
  • 43
1

I stumbled upon a similar problem as OP. Unfortunately the accepted answer did not work for me since the content of the collectionView would not be centered properly. Therefore I came up with a different solution which only requires that all items in the collectionView are of the same width, which seems to be the case in the question:

#define cellSize 90

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    float width = collectionView.frame.size.width;
    float spacing = [self collectionView:collectionView layout:collectionViewLayout minimumInteritemSpacingForSectionAtIndex:section];
    int numberOfCells = (width + spacing) / (cellSize + spacing);
    int inset = (width + spacing - numberOfCells * (cellSize + spacing) ) / 2;

    return UIEdgeInsetsMake(0, inset, 0, inset);
}

That code will ensure that the value returned by ...minimumInteritemSpacing... will be the exact spacing between every collectionViewCell and furthermore guarantee that the cells all together will be centered in the collectionView

luk2302
  • 55,258
  • 23
  • 97
  • 137
1

The above solution by vojtech-vrbka is correct but it triggers a warning:

warning:UICollectionViewFlowLayout has cached frame mismatch for index path - cached value: This is likely occurring because the flow layout subclass Layout is modify attributes returned by UICollectionViewFlowLayout without copying them

The following code should fix it:

class CustomViewFlowLayout : UICollectionViewFlowLayout {

   let cellSpacing:CGFloat = 4

   override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
       let original = super.layoutAttributesForElementsInRect(rect)

       if let original = original {
           let attributes = NSArray.init(array: original, copyItems: true) as! [UICollectionViewLayoutAttributes]

           for (index, attribute) in attributes.enumerate() {
                if index == 0 { continue }
                let prevLayoutAttributes = attributes[index - 1]
                let origin = CGRectGetMaxX(prevLayoutAttributes.frame)
                if(origin + cellSpacing + attribute.frame.size.width < self.collectionViewContentSize().width) {
                    attribute.frame.origin.x = origin + cellSpacing
                }
            }
            return attributes
       }
       return nil
   }
}
Community
  • 1
  • 1
torrao
  • 291
  • 8
  • 17
  • 2
    Like the other answers on SO that use this approach, this does work for fitting cells across the width of the collectionView, but intermittent, variable-height gaps still remain between rows. This may be be a limitation of the way CollectionView actually renders. – Womble Jun 13 '18 at 01:17
1

I have problem with the accepted answer, so I updated it, this is working for me:

.h

@interface MaxSpacingCollectionViewFlowLayout : UICollectionViewFlowLayout
@property (nonatomic,assign) CGFloat maxCellSpacing;
@end

.m

@implementation MaxSpacingCollectionViewFlowLayout

- (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *attributes = [super layoutAttributesForElementsInRect:rect];

    if (attributes.count <= 0) return attributes;

    CGFloat firstCellOriginX = ((UICollectionViewLayoutAttributes *)attributes[0]).frame.origin.x;

    for(int i = 1; i < attributes.count; i++) {
        UICollectionViewLayoutAttributes *currentLayoutAttributes = attributes[i];
        if (currentLayoutAttributes.frame.origin.x == firstCellOriginX) { // The first cell of a new row
            continue;
        }

        UICollectionViewLayoutAttributes *prevLayoutAttributes = attributes[i - 1];
        CGFloat prevOriginMaxX = CGRectGetMaxX(prevLayoutAttributes.frame);
        if ((currentLayoutAttributes.frame.origin.x - prevOriginMaxX) > self.maxCellSpacing) {
            CGRect frame = currentLayoutAttributes.frame;
            frame.origin.x = prevOriginMaxX + self.maxCellSpacing;
            currentLayoutAttributes.frame = frame;
        }
    }
    return attributes;
}

@end
Yun CHEN
  • 6,450
  • 3
  • 30
  • 33
1

Swift 3 Version

Simply create a UICollectionViewFlowLayout subclass and paste this method.

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard let answer = super.layoutAttributesForElements(in: rect) else { return nil }

        for i in 1..<answer.count {
            let currentAttributes = answer[i]
            let previousAttributes = answer[i - 1]

            let maximumSpacing: CGFloat = 8
            let origin = previousAttributes.frame.maxX

            if (origin + maximumSpacing + currentAttributes.frame.size.width < self.collectionViewContentSize.width && currentAttributes.frame.origin.x > previousAttributes.frame.origin.x) {
                var frame = currentAttributes.frame
                frame.origin.x = origin + maximumSpacing
                currentAttributes.frame = frame
            }
        }

        return answer
}
topLayoutGuide
  • 1,407
  • 11
  • 22
  • This works for horizontal fitting, but gaps will remain between rows, even if minimumLineSpacingForSectionAt is set to return 0, and layout.minimumLineSpacing is set to 0. Also, a touch in the collectionView will crash the app, due to the first loop being out of range. – Womble Jun 13 '18 at 01:13
1

I have tried iago849's answer and it worked.

Swift 4

open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    guard let answer = super.layoutAttributesForElements(in: rect) else {
        return nil
    }
    let count = answer.count
    for i in 1..<count {
        let currentLayoutAttributes = answer[i]
        let prevLayoutAttributes = answer[i-1]
        let origin = prevLayoutAttributes.frame.maxX
        if (origin + CGFloat(spacing) + currentLayoutAttributes.frame.size.width) < self.collectionViewContentSize.width && currentLayoutAttributes.frame.origin.x > prevLayoutAttributes.frame.origin.x {
            var frame = currentLayoutAttributes.frame
            frame.origin.x = origin + CGFloat(spacing)
            currentLayoutAttributes.frame = frame
        }
    }
    return answer
}

Here is the link for the github project. https://github.com/vishalwaka/MultiTags

vishalwaka
  • 89
  • 1
  • 12
1

Swift 5 UIKit Programmatically

 //Create UICollectionView
lazy var collectionView: UICollectionView = {
    
    let layout = UICollectionViewFlowLayout()
    layout.scrollDirection = .horizontal
    
    //CollectionCellView width autoSize
    layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
    collectionView.translatesAutoresizingMaskIntoConstraints = false
    
    collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
    collectionView.backgroundColor = .clear
    collectionView.delegate = self
    collectionView.dataSource = self
    collectionView.alwaysBounceHorizontal = true

//Add Spacing in each cell
    layout.minimumInteritemSpacing = 15
    layout.minimumLineSpacing = 15
    
    return collectionView
}()
Chithian
  • 253
  • 3
  • 8
0

Previous versions did not really work with sections > 1. So my solution was found here https://codentrick.com/create-a-tag-flow-layout-with-uicollectionview/. For the lazy ones:

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    let attributesForElementsInRect = super.layoutAttributesForElements(in: rect)
    var newAttributesForElementsInRect = [UICollectionViewLayoutAttributes]()
    // use a value to keep track of left margin
    var leftMargin: CGFloat = 0.0;
    for attributes in attributesForElementsInRect! {
        let refAttributes = attributes 
        // assign value if next row
        if (refAttributes.frame.origin.x == self.sectionInset.left) {
            leftMargin = self.sectionInset.left
        } else {
            // set x position of attributes to current margin
            var newLeftAlignedFrame = refAttributes.frame
            newLeftAlignedFrame.origin.x = leftMargin
            refAttributes.frame = newLeftAlignedFrame
        }
        // calculate new value for current margin
        leftMargin += refAttributes.frame.size.width + 10
        newAttributesForElementsInRect.append(refAttributes)
    }

    return newAttributesForElementsInRect
}
-1

Try playing around with this method:

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView
                    layout:(UICollectionViewLayout*)collectionViewLayout
    insetForSectionAtIndex:(NSInteger)section {

    UIEdgeInsets insets = UIEdgeInsetsMake(?, ?, ?, ?);

    return insets;
}
Nikola Kirev
  • 3,152
  • 3
  • 22
  • 30