How do you determine spacing between cells in UICo

2019-01-07 02:09发布

I have a UICollectionView with a flow layout and each cell is a square. How do I determine the spacing between each cells on each row? I can't seem to find the appropriate settings for this. I see there's a min spacing attributes on the nib file for a collection view, but I set this to 0 and the cells doesn't even stick.

enter image description here

Any other idea?

11条回答
成全新的幸福
2楼-- · 2019-01-07 03:01

Here it is for NSCollectionViewFlowLayout

class LeftAlignedCollectionViewFlowLayout: NSCollectionViewFlowLayout {

    var maximumCellSpacing = CGFloat(2.0)

    override func layoutAttributesForElementsInRect(rect: NSRect) -> [NSCollectionViewLayoutAttributes] {

        let attributesToReturn = super.layoutAttributesForElementsInRect(rect)

        for attributes in attributesToReturn ?? [] {
            if attributes.representedElementKind == nil {
                attributes.frame = self.layoutAttributesForItemAtIndexPath(attributes.indexPath!)!.frame
            }
        }
        return attributesToReturn
    }

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> NSCollectionViewLayoutAttributes? {

        let curAttributes = super.layoutAttributesForItemAtIndexPath(indexPath)
        let sectionInset = (self.collectionView?.collectionViewLayout as! NSCollectionViewFlowLayout).sectionInset

        if indexPath.item == 0 {
            let f = curAttributes!.frame
            curAttributes!.frame = CGRectMake(sectionInset.left, f.origin.y, f.size.width, f.size.height)
            return curAttributes
        }

        let prevIndexPath = NSIndexPath(forItem: indexPath.item-1, inSection: indexPath.section)
        let prevFrame = self.layoutAttributesForItemAtIndexPath(prevIndexPath)!.frame
        let prevFrameRightPoint = prevFrame.origin.x + prevFrame.size.width + maximumCellSpacing

        let curFrame = curAttributes!.frame
        let stretchedCurFrame = CGRectMake(0, curFrame.origin.y, self.collectionView!.frame.size.width, curFrame.size.height)

        if CGRectIntersectsRect(prevFrame, stretchedCurFrame) {
            curAttributes!.frame = CGRectMake(prevFrameRightPoint, curFrame.origin.y, curFrame.size.width, curFrame.size.height)
        } else {
            curAttributes!.frame = CGRectMake(sectionInset.left, curFrame.origin.y, curFrame.size.width, curFrame.size.height)
        }

        return curAttributes
    }
}
查看更多
爱情/是我丢掉的垃圾
3楼-- · 2019-01-07 03:03

Clean Swift solution, from an history of evolution:

  1. there was matt answer
  2. there was Chris Wagner lone items fix
  3. there was mokagio sectionInset and minimumInteritemSpacing improvement
  4. there was fanpyi Swift version
  5. now here is a simplified and clean version of mine:
open class UICollectionViewLeftAlignedLayout: UICollectionViewFlowLayout {
    open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        return super.layoutAttributesForElements(in: rect)?.map { $0.representedElementKind == nil ? layoutAttributesForItem(at: $0.indexPath)! : $0 }
    }

    open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let currentItemAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes,
            collectionView != nil else {
            // should never happen
            return nil
        }

        // if the current frame, once stretched to the full row intersects the previous frame then they are on the same row
        if indexPath.item != 0,
            let previousFrame = layoutAttributesForItem(at: IndexPath(item: indexPath.item - 1, section: indexPath.section))?.frame,
            currentItemAttributes.frame.intersects(CGRect(x: -.infinity, y: previousFrame.origin.y, width: .infinity, height: previousFrame.size.height)) {
            // the next item on a line
            currentItemAttributes.frame.origin.x = previousFrame.origin.x + previousFrame.size.width + evaluatedMinimumInteritemSpacingForSection(at: indexPath.section)
        } else {
            // the first item on a line
            currentItemAttributes.frame.origin.x = evaluatedSectionInsetForSection(at: indexPath.section).left
        }
        return currentItemAttributes
    }

    func evaluatedMinimumInteritemSpacingForSection(at section: NSInteger) -> CGFloat {
        return (collectionView?.delegate as? UICollectionViewDelegateFlowLayout)?.collectionView?(collectionView!, layout: self, minimumInteritemSpacingForSectionAt: section) ?? minimumInteritemSpacing
    }

    func evaluatedSectionInsetForSection(at index: NSInteger) -> UIEdgeInsets {
        return (collectionView?.delegate as? UICollectionViewDelegateFlowLayout)?.collectionView?(collectionView!, layout: self, insetForSectionAt: index) ?? sectionInset
    }
}

Usage: the spacing between items is determined by delegate's collectionView (_:layout:minimumInteritemSpacingForSectionAt:).

I put it on github, https://github.com/Coeur/UICollectionViewLeftAlignedLayout, where I actually added a feature of supporting both scroll directions (horizontal and vertical).

查看更多
我只想做你的唯一
4楼-- · 2019-01-07 03:04

The "problem" with UICollectionViewFlowLayout is that it applies a justified align to the cells: The first cell in a row is left-aligned, the last cell in a row is right-aligned and all other cells in between are evenly distributed with an equal spacing that's greater than the minimumInteritemSpacing.

There are already many great answers to this post that solve this problem by subclassing UICollectionViewFlowLayout. As a result you get a layout that aligns the cells left. Another valid solution to distribute the cells with a constant spacing is to align the cells right.

AlignedCollectionViewFlowLayout

I've created a UICollectionViewFlowLayout subclass as well that follows a similar idea as suggested by matt and Chris Wagner that can either align the cells

⬅︎ left:

Left-aligned layout

or ➡︎ right:

Right-aligned layout

You can simply download it from here, add the layout file to your project and set AlignedCollectionViewFlowLayout as your collection view's layout class:
https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout

How it works (for left-aligned cells):

 +---------+----------------------------------------------------------------+---------+
 |         |                                                                |         |
 |         |     +------------+                                             |         |
 |         |     |            |                                             |         |
 | section |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| section |
 |  inset  |     |intersection|        |                     |   line rect  |  inset  |
 |         |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -|         |
 | (left)  |     |            |             current item                    | (right) |
 |         |     +------------+                                             |         |
 |         |     previous item                                              |         |
 +---------+----------------------------------------------------------------+---------+

The concept here is to check if the current cell with index i and the previous cell with the index i−1 occupy the same line.

  • If they don't the cell with index i is the left most cell in the line.
    → Move the cell to the left edge of the collection view (without changing its vertical position).
  • If they do, the cell with index i is not the left most cell in the line.
    → Get the previous cell's frame (with the index i−1) and move the current cell next to it.

For right-aligned cells...

... you do the same vice-versa, i.e. you check the next cell with the index i+1 instead.

查看更多
叼着烟拽天下
5楼-- · 2019-01-07 03:07

To get a maximum interitem spacing, subclass UICollectionViewFlowLayout and override layoutAttributesForElementsInRect: and layoutAttributesForItemAtIndexPath:.

For example, a common problem is this: the rows of a collection view are right-and-left justified, except for the last line which is left-justified. Let's say we want all the lines to be left-justified, so that the space between them is, let's say, 10 points. Here's an easy way (in your UICollectionViewFlowLayout subclass):

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray* arr = [super layoutAttributesForElementsInRect:rect];
    for (UICollectionViewLayoutAttributes* atts in arr) {
        if (nil == atts.representedElementKind) {
            NSIndexPath* ip = atts.indexPath;
            atts.frame = [self layoutAttributesForItemAtIndexPath:ip].frame;
        }
    }
    return arr;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes* atts =
    [super layoutAttributesForItemAtIndexPath:indexPath];

    if (indexPath.item == 0) // degenerate case 1, first item of section
        return atts;

    NSIndexPath* ipPrev =
    [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];

    CGRect fPrev = [self layoutAttributesForItemAtIndexPath:ipPrev].frame;
    CGFloat rightPrev = fPrev.origin.x + fPrev.size.width + 10;
    if (atts.frame.origin.x <= rightPrev) // degenerate case 2, first item of line
        return atts;

    CGRect f = atts.frame;
    f.origin.x = rightPrev;
    atts.frame = f;
    return atts;
}

The reason this is so easy is that we aren't really performing the heavy lifting of the layout; we are leveraging the layout work that UICollectionViewFlowLayout has already done for us. It has already decided how many items go in each line; we're just reading those lines and shoving the items together, if you see what I mean.

查看更多
神经病院院长
6楼-- · 2019-01-07 03:14

There are a few things to consider:

  1. Try changing the minimum spacing in IB, but leave the cursor in that field. Notice that Xcode doesn't immediately mark the document as changed. When you click in a different field, though, Xcode does notice that the document is changed and marks it so in the file navigator. So, be sure to tab or click over to a different field after making a change.

  2. Save your storyboard/xib file after making a change, and be sure to rebuild the app. It's not hard to miss that step, and then you're left scratching your head wondering why your changes didn't seem to have any effect.

  3. UICollectionViewFlowLayout has a minimumInteritemSpacing property, which is what you're setting in IB. But the collection's delegate can also have a method to determine the inter-item spacing. That method trump's the layout's property, so if you implement it in your delegate your layout's property won't be used.

  4. Remember that the spacing there is a minimum spacing. The layout will use that number (whether it comes from the property or from the delegate method) as the smallest allowable space, but it may use a larger space if it has space leftover on the line. So if, for example, you set the minimum spacing to 0, you may still see a few pixels between items. If you want more control over exactly how the items are spaced you should probably use a different layout (possibly one of your own creation).

查看更多
登录 后发表回答