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条回答
Melony?
2楼-- · 2019-01-07 02:48

An easy way to left-justify is to modify layoutAttributesForElementsInRect: in your subclass of UICollectionViewFlowLayout:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *allLayoutAttributes = [super layoutAttributesForElementsInRect:rect];
    CGRect prevFrame = CGRectMake(-FLT_MAX, -FLT_MAX, 0, 0);
    for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes)
    {
        //fix blur
        CGRect theFrame = CGRectIntegral(layoutAttributes.frame);

        //left justify
        if(prevFrame.origin.x > -FLT_MAX &&
           prevFrame.origin.y >= theFrame.origin.y &&
           prevFrame.origin.y <= theFrame.origin.y) //workaround for float == warning
        {
            theFrame.origin.x = prevFrame.origin.x +
                                prevFrame.size.width + 
                                EXACT_SPACE_BETWEEN_ITEMS;
        }
        prevFrame = theFrame;

        layoutAttributes.frame = theFrame;
    }
    return allLayoutAttributes;
}
查看更多
别忘想泡老子
3楼-- · 2019-01-07 02:50

The swift version of Chris solution.

class PazLeftAlignedCollectionViewFlowLayout : UICollectionViewFlowLayout {
    var maxCellSpacing = 14.0
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
        if var attributesToReturn = super.layoutAttributesForElementsInRect(rect) as? Array<UICollectionViewLayoutAttributes> {
            for attributes in attributesToReturn {
                if attributes.representedElementKind == nil {
                    let indexPath = attributes.indexPath
                    attributes.frame = self.layoutAttributesForItemAtIndexPath(indexPath).frame;
                }
            }
            return attributesToReturn;
        }
        return super.layoutAttributesForElementsInRect(rect)
    }

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
        let currentItemAttributes = super.layoutAttributesForItemAtIndexPath(indexPath)

        if let collectionViewFlowLayout = self.collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
            let sectionInset = collectionViewFlowLayout.sectionInset
            if (indexPath.item == 0) { // first item of section
                var frame = currentItemAttributes.frame;
                frame.origin.x = sectionInset.left; // first item of the section should always be left aligned
                currentItemAttributes.frame = frame;

                return currentItemAttributes;
            }

            let previousIndexPath = NSIndexPath(forItem:indexPath.item-1, inSection:indexPath.section)
            let previousFrame = self.layoutAttributesForItemAtIndexPath(previousIndexPath).frame;
            let previousFrameRightPoint = Double(previousFrame.origin.x) + Double(previousFrame.size.width) + self.maxCellSpacing

            let currentFrame = currentItemAttributes.frame
            var width : CGFloat = 0.0
            if let collectionViewWidth = self.collectionView?.frame.size.width {
                width = collectionViewWidth
            }
            let strecthedCurrentFrame = CGRectMake(0,
                currentFrame.origin.y,
                width,
                currentFrame.size.height);

            if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) { // if current item is the first item on the line
                // the approach here is to take the current frame, left align it to the edge of the view
                // then stretch it the width of the collection view, if it intersects with the previous frame then that means it
                // is on the same line, otherwise it is on it's own new line
                var frame = currentItemAttributes.frame;
                frame.origin.x = sectionInset.left; // first item on the line should always be left aligned
                currentItemAttributes.frame = frame;
                return currentItemAttributes;
            }

            var frame = currentItemAttributes.frame;
            frame.origin.x = CGFloat(previousFrameRightPoint)
            currentItemAttributes.frame = frame;
        }
        return currentItemAttributes;
    }
}

To use it do the following:

    override func viewDidLoad() {
    super.viewDidLoad()
    self.collectionView.collectionViewLayout = self.layout
}
var layout : PazLeftAlignedCollectionViewFlowLayout {
    var layout = PazLeftAlignedCollectionViewFlowLayout()
    layout.itemSize = CGSizeMake(220.0, 230.0)
    layout.minimumLineSpacing = 12.0
    return layout
}
查看更多
我命由我不由天
4楼-- · 2019-01-07 02:50

a swift version base on mokagio:https://github.com/fanpyi/UICollectionViewLeftAlignedLayout-Swift

class UICollectionViewLeftAlignedLayout: UICollectionViewFlowLayout {
    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributesToReturn = super.layoutAttributesForElementsInRect(rect)
        if let attributesToReturn = attributesToReturn {
            for attributes in attributesToReturn {
                if attributes.representedElementKind == nil {
                    let indexpath = attributes.indexPath
                    if let attr = layoutAttributesForItemAtIndexPath(indexpath) {
                        attributes.frame = attr.frame
                    }

                }
            }
        }
        return attributesToReturn
    }

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
        if  let currentItemAttributes = super.layoutAttributesForItemAtIndexPath(indexPath){
            let sectionInset = self.evaluatedSectionInsetForItemAtIndex(indexPath.section)
            let  isFirstItemInSection = indexPath.item == 0;
            let  layoutWidth = CGRectGetWidth(self.collectionView!.frame) - sectionInset.left - sectionInset.right;

            if (isFirstItemInSection) {
                currentItemAttributes.leftAlignFrameWithSectionInset(sectionInset)
                return currentItemAttributes
            }

            let  previousIndexPath = NSIndexPath(forItem: indexPath.item - 1, inSection: indexPath.section)

            let previousFrame = layoutAttributesForItemAtIndexPath(previousIndexPath)?.frame ?? CGRectZero
            let  previousFrameRightPoint = previousFrame.origin.x + previousFrame.width
            let currentFrame = currentItemAttributes.frame;
            let  strecthedCurrentFrame = CGRectMake(sectionInset.left,
                currentFrame.origin.y,
                layoutWidth,
                currentFrame.size.height)
            // if the current frame, once left aligned to the left and stretched to the full collection view
            // widht intersects the previous frame then they are on the same line
            let  isFirstItemInRow = !CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)

            if (isFirstItemInRow) {
                // make sure the first item on a line is left aligned
                currentItemAttributes.leftAlignFrameWithSectionInset(sectionInset)
                return currentItemAttributes
            }

            var frame = currentItemAttributes.frame;
            frame.origin.x = previousFrameRightPoint + evaluatedMinimumInteritemSpacingForSectionAtIndex(indexPath.section)
            currentItemAttributes.frame = frame;
            return currentItemAttributes;

        }
        return nil
    }
    func evaluatedMinimumInteritemSpacingForSectionAtIndex(sectionIndex:Int) -> CGFloat {
        if let delegate = self.collectionView?.delegate as? UICollectionViewDelegateFlowLayout {
            if delegate.respondsToSelector("collectionView:layout:minimumInteritemSpacingForSectionAtIndex:") {
                return delegate.collectionView!(self.collectionView!, layout: self, minimumInteritemSpacingForSectionAtIndex: sectionIndex)

            }
        }
        return self.minimumInteritemSpacing

    }
    func evaluatedSectionInsetForItemAtIndex(index: Int) ->UIEdgeInsets {
        if let delegate = self.collectionView?.delegate as? UICollectionViewDelegateFlowLayout {
            if  delegate.respondsToSelector("collectionView:layout:insetForSectionAtIndex:") {
                return delegate.collectionView!(self.collectionView!, layout: self, insetForSectionAtIndex: index)
            }
        }
        return self.sectionInset
    }
}
查看更多
聊天终结者
5楼-- · 2019-01-07 02:52

A little bit of maths does the trick more easily. The code wrote by Chris Wagner is horrible because it calls the layout attributes of each previous items. So the more you scroll, the more it's slow...

Just use modulo like this (I'm using my minimumInteritemSpacing value as a max value too):

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes* currentItemAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
    NSInteger numberOfItemsPerLine = floor([self collectionViewContentSize].width / [self itemSize].width);

    if (indexPath.item % numberOfItemsPerLine != 0)
    {
        NSInteger cellIndexInLine = (indexPath.item % numberOfItemsPerLine);

        CGRect itemFrame = [currentItemAttributes frame];
        itemFrame.origin.x = ([self itemSize].width * cellIndexInLine) + ([self minimumInteritemSpacing] * cellIndexInLine);
        currentItemAttributes.frame = itemFrame;
    }

    return currentItemAttributes;
}
查看更多
男人必须洒脱
6楼-- · 2019-01-07 02:53

Update: Swift version of this answer: https://github.com/fanpyi/UICollectionViewLeftAlignedLayout-Swift


Taking @matt's lead I modified his code to insure that items are ALWAYS left aligned. I found that if an item ended up on a line by itself, it would be centered by the flow layout. I made the following changes to address this issue.

This situation would only ever occur if you have cells that vary in width, which could result in a layout like the following. The last line always left aligns due to the behavior of UICollectionViewFlowLayout, the issue lies in items that are by themselves in any line but the last one.

With @matt's code I was seeing.

enter image description here

In that example we see that cells get centered if they end up on the line by themselves. The code below insures your collection view would look like this.

enter image description here

#import "CWDLeftAlignedCollectionViewFlowLayout.h"

const NSInteger kMaxCellSpacing = 9;

@implementation CWDLeftAlignedCollectionViewFlowLayout

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

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

    UIEdgeInsets sectionInset = [(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout sectionInset];

    if (indexPath.item == 0) { // first item of section
        CGRect frame = currentItemAttributes.frame;
        frame.origin.x = sectionInset.left; // first item of the section should always be left aligned
        currentItemAttributes.frame = frame;

        return currentItemAttributes;
    }

    NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
    CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame;
    CGFloat previousFrameRightPoint = previousFrame.origin.x + previousFrame.size.width + kMaxCellSpacing;

    CGRect currentFrame = currentItemAttributes.frame;
    CGRect strecthedCurrentFrame = CGRectMake(0,
                                              currentFrame.origin.y,
                                              self.collectionView.frame.size.width,
                                              currentFrame.size.height);

    if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) { // if current item is the first item on the line
        // the approach here is to take the current frame, left align it to the edge of the view
        // then stretch it the width of the collection view, if it intersects with the previous frame then that means it
        // is on the same line, otherwise it is on it's own new line
        CGRect frame = currentItemAttributes.frame;
        frame.origin.x = sectionInset.left; // first item on the line should always be left aligned
        currentItemAttributes.frame = frame;
        return currentItemAttributes;
    }

    CGRect frame = currentItemAttributes.frame;
    frame.origin.x = previousFrameRightPoint;
    currentItemAttributes.frame = frame;
    return currentItemAttributes;
}

@end
查看更多
Juvenile、少年°
7楼-- · 2019-01-07 02:59

A cleaner swift version for people interested, based on Chris Wagner's answer:

class AlignLeftFlowLayout: UICollectionViewFlowLayout {

    var maximumCellSpacing = CGFloat(9.0)

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

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

        return attributesToReturn
    }

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
        let curAttributes = super.layoutAttributesForItemAtIndexPath(indexPath)
        let sectionInset = (self.collectionView?.collectionViewLayout as UICollectionViewFlowLayout).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
    }
}
查看更多
登录 后发表回答