We are using UICollectionViewFlowLayout commonly, be familiar with the grid view. You can also customize the UICollectionViewLayout to place each list element wherever you want.

For example: the left row of a fixed-space list

In this case, the system is not directly used to make, you need to customize a UICollectionViewLayout.

As a general rule, the new one UICollectionViewLeftAlignedLayout, inherited from UICollectionViewFlowLayout

Rewrite UICollectionViewFlowLayout usually take these two methods,

LayoutAttributesForElements (in), this method needs to be provided, all inside a given rectangle grid layout properties. The given rectangle area is the contentSize of the content view area of UICollectioonView.

LayoutAttributesForItem (at:): This method needs to provide the specific layout information that the grid view needs. We will override this method to return the layout properties of the cell at the required indexPath position.

Sometimes this property is overridden:

CollectionViewContentSize, generally we are the size of the content area, as computing attribute processing. It provides the width and height of the content area of the grid view. The content area of the grid view is not the visible area of the grid view.

Because the grid view, UICollectionView, inherits from UIScrollView. The grid view uses this property to configure it as the content view size of the slidable UIScrollView.

The main code is as follows:

The helper functions are not listed, as described in the Github repo at the end of this article.

class UICollectionViewLeftAlignedLayout: UICollectionViewFlowLayout {/ / this function did not do something, do things mainly is to call the function layoutAttributesForItem, access to information, Provide out override func layoutAttributesForElements (in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var attributesCopy: [UICollectionViewLayoutAttributes] = []
        if let attributes = super.layoutAttributesForElements(in: rect) {
            attributes.forEach({ attributesCopy.append($0.copy() as! UICollectionViewLayoutAttributes) })
        }
        for attributes in attributesCopy {
            if attributes.representedElementKind == nil {
                letIndexpath = attributes. Indexpath // Where things are doneif let attr = layoutAttributesForItem(at: indexpath) {
                    attributes.frame = attr.frame
                }
            }
        }
        returnOverride func layoutAttributesForItem(at indexPath: Override func layoutAttributesForItem) IndexPath) -> UICollectionViewLayoutAttributes? {if letcurrentItemAttributes = super.layoutAttributesForItem(at: indexPath as IndexPath)? .copy() as? UICollectionViewLayoutAttributes,let collection = collectionView {
            let sectionInset = evaluatedSectionInsetForItem(at: indexPath.section)
            let isFirstItemInSection = indexPath.item == 0
            letLayoutWidth = collection.frame.width -sectionInset. Left -sectionInset. Right // Make the first element of each row the first one, in two cases. That's the first one, the first element of this section, of course, is the first one. guard ! isFirstItemInSectionelse{
                currentItemAttributes.leftAlignFrame(with: sectionInset)
                return currentItemAttributes
            }
            let previousIndexPath = IndexPath(item: indexPath.item - 1, section: indexPath.section)
            letpreviousFrame = layoutAttributesForItem(at: previousIndexPath)? .frame ?? CGRect.zerolet previousFrameRightPoint = previousFrame.origin.x + previousFrame.width
            letCurrentFrame = currentItemAttributes. Frame / / strecthedCurrentFrame is this line of the current grid area size, to pull out. StrecthedCurrentFrame = strecthedCurrentFrame = strecthedCurrentFrame = strecthedCurrentFrame If there's overlap, it's on the same line.let strecthedCurrentFrame = CGRect(x: sectionInset.left,
                                                    y: currentFrame.origin.y,
                                                    width: layoutWidth,
                                                    height: currentFrame.size.height)
            letisFirstItemInRow = ! Previousframe.intersects (strecthedCurrentFrame) // So that the first element of each row is placed at the top. This is the second type, the other headers of this section, calculated as: the previous grid is on the previous row, not the current row, guard! isFirstItemInRowelse{
                currentItemAttributes.leftAlignFrame(with: sectionInset)
                returnCurrentItemAttributes} // The rest, simple. Get rid of it all. The remaining cells are not in a row, and the last fixed spacing is finished. var frame = currentItemAttributes.frame frame.origin.x = previousFrameRightPoint + evaluatedMinimumInteritemSpacing(at: indexPath.section) currentItemAttributes.frame = framereturn currentItemAttributes
            
        }
        return nil
    }
// ...
}

Copy the code

The wayfunc layoutAttributesForItem(at indexPath: IndexPath)Design ideas.

Because if you use UICollectionViewFlowLayout, doing nothing at all, and the difference between the above it. The number of elements in each row is the same, and the details are the same, which cells are centered. After all, minimumInteritemSpacing means minimum in-line spacing, not fixed in-line spacing.

And then you move it around, and you’re done. Place the first element of each row at the head, and the rest of each row at a fixed distance from the previous element, and you’re done.

Example 2: Right row of a fixed-space list

Analysis: It looks like the right row is pushing the left elements to the right. This is the left row of elements, with the current row added to each elementOffsetX.

Frame.width – the last frame.maxx of the elements arrayed to the left

This is the other case. Because you can’t change the layoutAttributesForItem method on the left, that’s fine. The left row is previous, no problem.

For each element, find Next until X + width > width of the collectionView.

If layoutAttributesForItem is used to find next, there is a rather annoying recursion: now find next, next find next. Actually, the same thing happens in the first case, current looking for previous, previous looking for previous.

The difference is that Apple optimized next to next, because I’m in func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: indexPath) -> CGSize returns the size width randomly.

And then all hell broke loose. Next will return a random size, and next will return another random size.

What I do is I do it twice, first the left row, and then based on the result of the left row, I add OffsetX to make it the right row.

// I used the lock and refresh the layout every second while testing. A lot of times, will appear on the Mac multi-core CPU asynchronous drawing results are not very goodletLock = NSLock () / / because is twice, the first time will not be able to put in func layoutAttributesForElements (inThe rect: CGRect) method, / / I put in func prepare () method, using the create UICollectionViewLayoutAttributes override funcprepare() {
        lock.lock()
        contentHeight = 0
        cache.removeAll()
        storedCellSize.removeAll()
        guard let collectionView = collectionView else {
            return
        }
        var currentXOffset:CGFloat = 0
        var nextXOffset:CGFloat = 0
        var currentYOffset:CGFloat = 0
        var nextYOffset:CGFloat = 0
        
        
        for section in0.. <collectionView.numberOfSections{let sectionInset = evaluatedSectionInsetForItem(at: section)
            nextXOffset = sectionInset.left
            nextYOffset += sectionInset.top
            let count = collectionView.numberOfItems(inSection: section)
            for item in0.. <count{ currentXOffset = nextXOffset currentYOffset = nextYOffsetletindexPath = IndexPath(item: item, section: Section) / / used to create UICollectionViewLayoutAttributes, not access the system super. LayoutAttributesForItem (ats)let currentItemAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
                let currentIndexPathSize = queryItemSize(indexPath)
                letcurrentItemInNext = (currentXOffset + evaluatedMinimumInteritemSpacing(at: Section) + currentIndexPathSize. Width) > (collectionView. Frame. The width - sectionInset. Right + 0.1)if currentItemInNext{
                    currentXOffset = sectionInset.left
                    currentYOffset += (currentIndexPathSize.height + evaluatedMinimumLineSpacing(at: section))
                    
                    nextXOffset = currentXOffset + (currentIndexPathSize.width + evaluatedMinimumInteritemSpacing(at: section))
                   
                    nextYOffset = currentYOffset
                }else{
                    nextXOffset += (currentIndexPathSize.width + evaluatedMinimumInteritemSpacing(at: section))
                }
                
                
                letframe = CGRect(origin: CGPoint(x: currentXOffset, y: currentYOffset), size: currentIndexPathSize) currentItemAttributes.frame = frame cache[indexPath] = currentItemAttributes contentHeight = Max (contentHeight, frame.maxy)} nextYOffset = contentHeight} lock.unlock()} This function is not doing anything, Main layoutAttributesForItem do things is to call the function, and access to information, and provide out override func layoutAttributesForElements (in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        
        var attributesCopy: [UICollectionViewLayoutAttributes] = []
        if let attributes = super.layoutAttributesForElements(in: rect) {
            attributes.forEach({ attributesCopy.append($0.copy() as! UICollectionViewLayoutAttributes) })
        }
        for attributes in attributesCopy {
            if attributes.representedElementKind == nil {
                let indexpath = attributes.indexPath
                if let attr = layoutAttributesForItem(at: indexpath) {
                    attributes.frame = attr.frame
                }
            }
        }
        returnAttributesCopy} // This function is computed the second time. Override func layoutAttributesForItem(at indexPath: override func layoutAttributesForItem) IndexPath) -> UICollectionViewLayoutAttributes? { guardlet collectionView = collectionView,let attribute = cache[indexPath] else {
            return nil
        }
        
        var offsetX: CGFloat = attribute.frame.maxX
        letcriteria = collectionView.frame.width - evaluatedSectionInsetForItem(at: IndexPath.section). Right-0.1 var gap = criteria - offsetX var IP = indexPathlet sectionCount = collectionView.numberOfItems(inSection: indexPath.section)
        whileIp. item < sectionCount{// The result is stable. The same row, the y-coordinate, of course, is the same. var conditionSecond =false
            if letnextAttri = cache[ip.next]{ conditionSecond = nextAttri.frame.minY ! = attribute.frame.minY }if (ip.item + 1) >= sectionCount || conditionSecond {
                gap = criteria - offsetX
                break
            }
            else{ ip = ip.next offsetX += (evaluatedMinimumInteritemSpacing(at: indexPath.section) + cache[ip]! .frame.width) } } attribute.trailingAlignFrame(with: gap)return attribute
        
    }
Copy the code

Here we use the prepare() method, which is called when a layout operation is performed. We use this opportunity to calculate the size provided to the collectionView and the positions of these cells.

Example 3, the right side is reversed

As in example one, you put the first element of each row on the far right, and you just keep the rest of the cells fairly spaced apart.

The code is as follows:

/ / this function did not do something, do things mainly is to call the function layoutAttributesForItem, access to information, and provide out override func layoutAttributesForElements (in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        
        var attributesCopy: [UICollectionViewLayoutAttributes] = []
        if let attributes = super.layoutAttributesForElements(in: rect) {
            attributes.forEach({ attributesCopy.append($0.copy() as! UICollectionViewLayoutAttributes) })
        }
        
        for attributes in attributesCopy {
            if attributes.representedElementKind == nil {
                let indexpath = attributes.indexPath
                if let attr = layoutAttributesForItem(at: indexpath) {
                    attributes.frame = attr.frame
                }
            }
        }
        return attributesCopy
    }
    
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        
        if letcurrentItemAttributes = super.layoutAttributesForItem(at: indexPath as IndexPath)? .copy() as? UICollectionViewLayoutAttributes ,let collection = collectionView{
            letIsFirstItemInSection = indexPath. Item == 0if isFirstItemInSection {
                currentItemAttributes.rightAlignFrame(with: collection.frame.size.width)
                return currentItemAttributes
            }
            
            let previousIndexPath = IndexPath(item: indexPath.item - 1, section: indexPath.section)
            
            letpreviousFrame = layoutAttributesForItem(at: previousIndexPath)? .frame ?? CGRect.zerolet currentFrame = currentItemAttributes.frame
            let strecthedCurrentFrame = CGRect(x: 0,
                                                    y: currentFrame.origin.y,
                                                    width: collection.frame.size.width,
                                                    height: currentFrame.size.height)
            letisFirstItemInRow = ! PreviousFrame. Intersects (strecthedCurrentFrame) // Case twoif isFirstItemInRow {
                currentItemAttributes.rightAlignFrame(with: collection.frame.size.width)
                returnCurrentItemAttributes} // The normal case on the rightlet previousFrameLeftPoint = previousFrame.origin.x
            var frame = currentItemAttributes.frame
            let minimumInteritemSpacing = evaluatedMinimumInteritemSpacing(at: indexPath.item)
            frame.origin.x = previousFrameLeftPoint - minimumInteritemSpacing - frame.size.width
            currentItemAttributes.frame = frame
            return currentItemAttributes
            
        }
        return nil
    }

Copy the code

More:

github: BoxDengJZ/UICollectionViewLeftAlignedLayout

StackOverFlow: How do you determine spacing between cells in UICollectionView flowLayout