0
votes

iOS UICollectionView how to Create Horizontal Scrolling rectangular layout with different size of items inside.

I want to create a Rectangular layout using UICollectionView like below. how can i achieve?

When i scroll horizontally using CollectionView 1,2,3,4,5,6 grid will scroll together to bring 7.

The Below are the dimensions of 320*480 iPhone Resolution. Updated Screen below.

enter image description here

First 6 items have below dimensions for iPhone 5s.

Item 1 Size is - (213*148)
Item 2 Size is - (106*75)
Item 3 Size is - (106*74)
Item 4 Size is - (106*88)
Item 5 Size is - (106*88)
Item 6 Size is - (106*88)

After item6 have same dimensions as collection View width and height like below.

Item 7 Size is - (320*237)
Item 8 Size is - (320*237)
Item 9 Size is - (320*237)

How to create a simple custom Layout Using collection view, that has horizontal scrolling?

Must appreciate for a quick solution. Thanks in advance.

3
Does this mean, when you scroll 1,2,3,4,5,6 grid will scroll together to bring 7 from right?chetan anand
@chetananand : YesPriya
@chetananand: thanks for quick reply. I am using cocoacontrols.com/controls/squareflowlayout. Is it possible to i can update in same?Priya
I'm not very familiar with this library, so not sure if the library supports this. Maybe you can try to tweak it a be to achieve this.chetan anand
You should use a custom Layout (see there stackoverflow.com/questions/42364859/… for an example). Only a custom one should be able to do what you want without using tricks (like separating a cell in two, etc.) Also, it's good to show in your schemas photos what are supposed to be the index cells, because it's harder to read them currently. I've worked on different one looking like yours, and depending on the client, the index would have different logic...Larme

3 Answers

1
votes

I would suggest using a StackView inside CollectionViewCell(of fixed dimension) to create a grid layout as shown in your post.

Below GridStackView creates a dynamic grid layout based on the number of views added using method addCell(view: UIView).

Add this GridStackView as the only subview of your CollectionViewCell pinning all the edges to the sides so that it fills the CollectionViewCell completely.

while preparing your CollectionViewCell, add tile views to it using the method addCell(view: UIView).

If only one view added, then it will show a single view occupying whole GridStackView and so as whole CollectionViewCell. If there is more than one view added, it will automatically layout them in the inside the CollectionViewCell.

You can tweak the code below to get the desired layout calculating the row and column. Current implementation needed rowSize to be supplied while initializing which I used for one of my project, you need to modify it a bit to get your desired layout.

class GridStackView: UIStackView {
    private var cells: [UIView] = []
    private var currentRow: UIStackView?
    var rowSize: Int = 3
    var defaultSpacing: CGFloat = 5

    init(rowSize: Int) {
        self.rowSize = rowSize
        super.init(frame: .zero)
        translatesAutoresizingMaskIntoConstraints = false
        axis = .vertical
        spacing = defaultSpacing
        distribution = .fillEqually
    }

    required init(coder: NSCoder) {
        super.init(coder: coder)
        translatesAutoresizingMaskIntoConstraints = false
        axis = .vertical
        spacing = defaultSpacing
        distribution = .fillEqually
    }

    private func preapreRow() -> UIStackView {
        let row = UIStackView(arrangedSubviews: [])
        row.spacing = defaultSpacing
        row.translatesAutoresizingMaskIntoConstraints = false
        row.axis = .horizontal
        row.distribution = .fillEqually
        return row
    }

    func removeAllCell() {
        for item in arrangedSubviews {
            item.removeFromSuperview()
        }
        cells.removeAll()
        currentRow = nil
    }

    func addCell(view: UIView) {
        let firstCellInRow = cells.count % rowSize == 0
        if currentRow == nil || firstCellInRow {
            currentRow = preapreRow()
            addArrangedSubview(currentRow!)
        }
        view.translatesAutoresizingMaskIntoConstraints = false
        cells.append(view)
        currentRow?.addArrangedSubview(view)
        setNeedsLayout()
    }
}

Layout with current implementation

0
votes
  • Create a new cell that contains two views. Views have equal width.
  • Contstruct your data accordingly

Data

struct ItemData {
    var color: [UIColor]
}

// NOTICE: 2nd item contains two colors and the rest one.
let data = [ItemData(color: [.red]), ItemData(color: [.blue, .purple]), ItemData(color: [.orange]),
            ItemData(color: [.cyan]), ItemData(color: [.green]), ItemData(color: [.magenta]),
            ItemData(color: [.systemPink]), ItemData(color: [.link]), ItemData(color: [.purple])] 

Cell

class CollectionViewCellOne: UICollectionViewCell {
    static let identifier = "CollectionViewCellOne"

    var item: ItemData? {
        didSet {
            if let item = item {
                self.leadingLabel.backgroundColor = item.color.first!
                self.trailingLabel.backgroundColor = item.color.last!
            }
        }
    }

    let leadingLabel = UILabel()
    let trailingLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.contentView.addSubview(leadingLabel)
        self.contentView.addSubview(trailingLabel)

        let width = self.frame.width / 2

        leadingLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        leadingLabel.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        leadingLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
        leadingLabel.widthAnchor.constraint(equalToConstant: width).isActive = true
        leadingLabel.translatesAutoresizingMaskIntoConstraints = false

        trailingLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        trailingLabel.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        trailingLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
        trailingLabel.widthAnchor.constraint(equalToConstant: width).isActive = true
        trailingLabel.translatesAutoresizingMaskIntoConstraints = false

    }

    required init?(coder: NSCoder) {
        fatalError()
    }
}

dequeueReusableCell

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    if indexPath.row == 1 {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCellOne.identifier, for: indexPath) as! CollectionViewCellOne
        cell.item = data[indexPath.row]
        return cell
    } else {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.identifier, for: indexPath) as! CollectionViewCell
        if let color = data[indexPath.row].color.first {
            cell.backgroundColor = color
        }

        return cell
    }
}
-1
votes

I have tried with Mahan's Answer and i am getting the partially Correct output. But the issue is, index1 having full width of two items.

How to split index 1 into index1 and Index2?

enter image description here

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        setUpCollectionView()
        // Do any additional setup after loading the view.
    }
    func setUpCollectionView() {
        self.view.backgroundColor = .white
        let layout = UICollectionViewFlowLayout()
//        layout.minimumInteritemSpacing = 1
//        layout.minimumLineSpacing = 1
        layout.scrollDirection = .horizontal
        let collectionView = CollectionView(frame: .zero, collectionViewLayout: layout)
        view.addSubview(collectionView)
        collectionView.bounces = false
        collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        collectionView.heightAnchor.constraint(equalToConstant: 240).isActive = true
        collectionView.translatesAutoresizingMaskIntoConstraints = false
    }

}
class CollectionView: UICollectionView {
    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
        self.register(CollectionViewCell.self, forCellWithReuseIdentifier: CollectionViewCell.identifier)
        self.dataSource = self
        self.delegate = self
        self.isPagingEnabled = true

    }

    required init?(coder: NSCoder) {
        fatalError()
    }

}
extension CollectionView: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.identifier, for: indexPath) as! CollectionViewCell
        cell.backgroundColor = .blue
        cell.label.text = "\(indexPath.row)"

        let row = indexPath.row
        switch row {
        case 0:
            cell.backgroundColor = .red
        case 1:
            cell.backgroundColor = .blue
        case 2:
            cell.backgroundColor = .purple
        case 3:
            cell.backgroundColor = .orange
        case 4:
            cell.backgroundColor = .cyan
        case 5:
            cell.backgroundColor = .green
        case 6:
            cell.backgroundColor = .magenta
        case 7:
            cell.backgroundColor = .white
        case 8:
            cell.backgroundColor = .blue
        case 9:
            cell.backgroundColor = .green
        default:
            cell.backgroundColor = .systemPink
        }
        return cell
    }


}

extension  CollectionView: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let row = indexPath.row
        let width = collectionView.frame.width
        let other = width / 3

        let height = collectionView.frame.height
        let o_height =  height / 3
        switch row {
        case 0:
            return CGSize(width: other * 2, height: o_height * 2)
        case 1:
            return CGSize(width: other * 2, height: o_height)
        case 2:
            return CGSize(width: other, height: o_height)
        case 3:
            return CGSize(width: other, height: o_height)
        case 4:
            return CGSize(width: other, height: o_height)
        case 5, 6, 7:
            return CGSize(width: other, height: o_height)
        default:
            return CGSize(width: width, height: height)
        }
    }

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



class CollectionViewCell: UICollectionViewCell {
    static let identifier = "CollectionViewCell"

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.contentView.addSubview(label)
        label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
        label.translatesAutoresizingMaskIntoConstraints = false
    }

    required init?(coder: NSCoder) {
        fatalError()
    }
}

How to devide index 1 into index1 and Index2 like below?

Thanks in advance!

enter image description here