3
votes

I've been trying to create a collection view that presents tags horizontally, in two row. The user can than scroll horizontally to view more tags. Exactly like the filters at the Bandcamp app https://itunes.apple.com/us/app/bandcamp/id706408639?mt=8

I found a very good tutorial on how to do something similar, by customizing UICollectionViewFlowLayout. https://codentrick.com/create-a-tag-flow-layout-with-uicollectionview/

However, this tutorial is meant for a vertical collection view, creating rows as needed. What I need is for the tags to flow right, and constrain the layout to two rows.

This is the snippet of the UICollectionViewFlowLayout from the tutorial

class FlowLayout: UICollectionViewFlowLayout {


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

    var leftMargin: CGFloat = 0.0;

    for attributes in attributesForElementsInRect! {
        if (attributes.frame.origin.x == self.sectionInset.left) {
            leftMargin = self.sectionInset.left
        } else {
            var newLeftAlignedFrame = attributes.frame
            newLeftAlignedFrame.origin.x = leftMargin
            attributes.frame = newLeftAlignedFrame
        }
        leftMargin += attributes.frame.size.width + 8
        newAttributesForElementsInRect.append(attributes)
    }

    return newAttributesForElementsInRect
  }
}

Thx!

3

3 Answers

3
votes

So, I finally managed to code a solution for my problem: on prepareLayout, I reassign X/Y position to my cells, as well as width of my content, then I update the attributes on LayoutAttributes. The trick is to have a variable that contains info for both rows, and change each cell accordingly. Here's the code for those who run into similar problem

import UIKit

class FlowLayout: UICollectionViewFlowLayout {

// SET SCROLL TO HORIZONTAL
override init(){
    super.init()
    scrollDirection = .Horizontal
}

required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)!
    self.scrollDirection = .Horizontal
}

// SET VARIABLES: Content height/width and layout attributes
private var contentWidth: CGFloat  = 0.0
private var contentHeight: CGFloat  = 50.0
private var cache = [UICollectionViewLayoutAttributes]()


override func prepareLayout() {
    super.prepareLayout()

    // RUNS ONLY ONCE
    if cache.isEmpty {
        var row = 0
        let numberOfRows = 2
        var rowWidth = [CGFloat](count: numberOfRows, repeatedValue: 0)
        var xOffset = [CGFloat](count: numberOfRows, repeatedValue: 0)

        for item in 0 ..< collectionView!.numberOfItemsInSection(0) {
            let indexPath = NSIndexPath(forItem: item, inSection: 0)
            let tag = super.layoutAttributesForItemAtIndexPath(indexPath)
            let attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)

            attributes.frame = tag!.frame
            attributes.frame.origin.x = xOffset[row]
            cache.append(attributes)

            rowWidth[row] = rowWidth[row] + tag!.frame.size.width + 8
            xOffset[row] = xOffset[row] + tag!.frame.size.width + 8
            row = row >= (numberOfRows - 1) ? 0 : 1
        }
        contentWidth = rowWidth.maxElement()!
    }
}

// SETS DYNAMIC WIDTH BASED ON TAGS
override func collectionViewContentSize() -> CGSize {
    return CGSize(width: contentWidth, height: contentHeight)
}

// UPDATES CELL ATTRIBUTES
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    var layoutAttributes = [UICollectionViewLayoutAttributes]()
    for attributes  in cache {
        if CGRectIntersectsRect(attributes.frame, rect ) {
            layoutAttributes.append(attributes)
        }
    }
    return layoutAttributes
}

override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
    return true
}


}
0
votes

Modified your code to be right aligned

class FlowLayout: UICollectionViewFlowLayout {

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

        var rightMargin: CGFloat = rect.width;

        for attribute in attributesForElementsInRect! {
            attribute.frame.origin.x = rightMargin - attribute.frame.width - 8

            if attribute.frame.origin.x <= self.sectionInset.left {
                rightMargin = rect.width - self.sectionInset.right
                attribute.frame.origin.x = rightMargin - attribute.frame.width
            }
            rightMargin = rightMargin - attribute.frame.size.width - 8
            newAttributesForElementsInRect.append(attribute)
        }

        return newAttributesForElementsInRect
    }
}
0
votes

I was able to modify a codebase, originally created by Włodzimierz Woźniak, so that I could horizontally scroll through tag-labels in a UITableViewCell. The programmatic addition of the UIScrollView as a subview in the UITableViewCell could almost certainly be implemented for a UICollectionViewFlowLayout.

Here is a breakdown of how the code works...

  1. Converting Strings to Labels
  2. Customizing Labels
  3. Converting Labels to Images
  4. Converting Images to Attributed text
  5. Creating TextView
  6. Setting Attributed text to TextView
  7. Adding TextView as a subview to ScollView
  8. Adding ScrollView as a subview to TableViewCell

You can find a workable demo of the implementation here:

https://github.com/austinmm/WordsAsTagLabels-Example enter image description here