Home > Software design >  How to show grid lines in Collecionview Swift
How to show grid lines in Collecionview Swift

Time:03-08

I am doing Swift application with custom collectionview. I would like to show columns rows in that and I have achieved it. But, I would like to show grid lines inside the cells.

Below is my code:

Viewcontroller class

import UIKit

private let reuseIdentifier = "cell"

class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
    
    @IBOutlet weak var collectionView: UICollectionView!

    var theData = [[String]]()
    override func viewDidLoad() {
        super.viewDidLoad()
        
        theData = [
            ["1", "Name", "TV", "LCD", "LED", ""],
            ["2", "Market", "No", "Charge", "Discount", ""],
            ["3", "value", "2.00", "05.00", "49.30", "200", ""],
            ["4", "Coupons", "1", "1","1","1","Total Price: "]]
        
        let layout = CustomLayout()
        collectionView?.collectionViewLayout = layout
        collectionView?.dataSource = self
        collectionView?.delegate = self
        layout.scrollDirection = .horizontal
        layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
        layout.minimumInteritemSpacing = 10
        layout.minimumLineSpacing = 10
        collectionView?.contentInsetAdjustmentBehavior = .always
        collectionView?.showsHorizontalScrollIndicator = false

    }
    
    // MARK: UICollectionViewDataSource
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return theData[section].count
    }
    
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return theData.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? CustomCollectionViewCell
        cell?.myLabel.text = theData[indexPath.section][indexPath.row]
        return cell!
    }
}

Custom collectionviewflowlayout class

import UIKit

class CustomLayout: UICollectionViewFlowLayout {

    let  itemWidth = 200
    let itemHeight = 200

   func collectionViewContentSize() -> CGSize {
       
        let xSize = (collectionView?.numberOfItems(inSection: 0))! * (itemWidth   2) // the 2 is for spacing between cells.
        let ySize = collectionView!.numberOfSections * (itemHeight   2)
        return CGSize(width: xSize, height: ySize)
    }
    
    override func layoutAttributesForItem(at path: IndexPath?) -> UICollectionViewLayoutAttributes? {
        
        var attributes: UICollectionViewLayoutAttributes? = nil
        if let path = path {
            attributes = UICollectionViewLayoutAttributes(forCellWith: path)
            var xValue: Int
            attributes?.size = CGSize(width: itemWidth, height: itemHeight)
            xValue = itemWidth / 2   (path.row ) * (itemWidth   2)
            let yValue = itemHeight   (path.section ) * (itemHeight   2)
            attributes?.center =  CGPoint(x:CGFloat(xValue), y:CGFloat(yValue))
        }
        return attributes
   }
    
    func layoutAttributesForElements(in rect: CGRect) -> [AnyHashable]? {
        
        let minRow = Int((rect.origin.x > 0) ? Int(rect.origin.x) / (itemWidth   2) : 0) // need to check because bounce gives negative values  for x.

        let maxRow = Int(Int(rect.size.width) / (itemWidth   2)   minRow)
        var attributes: [AnyHashable] = []
        for i in 0..<(self.collectionView?.numberOfSections)! {
            for j in (minRow..<maxRow) {
                let indexPath = IndexPath(item: j, section: i)
                attributes.append(layoutAttributesForItem(at: indexPath))
            }
        }
        return attributes
    }
}

Also I would like change background color only for header titles (1,2,3,4).

Any suggestions?

enter image description here

I would like to show like below screenshot gridlines enter image description here

CodePudding user response:

If you're going to have a "fixed grid" - that is, x-columns by y-rows - you can calculate your cell layout in prepare(), save the cellAttributes in an array, and then use that array for layoutAttributesForElements(in rect: CGRect):

class CustomLayout: UICollectionViewLayout {
    private var computedContentSize: CGSize = .zero
    private var cellAttributes = [IndexPath: UICollectionViewLayoutAttributes]()

    let itemWidth = 100
    let itemHeight = 60

    let gridLineWidth = 1
    
    override func prepare() {
        
        guard let collectionView = collectionView else {
            fatalError("not a collection view?")
        }
        
        // Clear out previous results
        computedContentSize = .zero
        cellAttributes = [IndexPath: UICollectionViewLayoutAttributes]()
        
        let numItems = collectionView.numberOfItems(inSection: 0)
        let numSections = collectionView.numberOfSections

        let widthPlusGridLineWidth = itemWidth   gridLineWidth
        let heightPlusGridLineWidth = itemHeight   gridLineWidth
        
        for section in 0 ..< numSections {
            for item in 0 ..< numItems {
                let itemFrame = CGRect(x: item * widthPlusGridLineWidth   gridLineWidth,
                                       y: section * heightPlusGridLineWidth   gridLineWidth,
                                       width: itemWidth, height: itemHeight)
                
                let indexPath = IndexPath(item: item, section: section)
                let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
                attributes.frame = itemFrame
                
                cellAttributes[indexPath] = attributes
            }
        }
        
        computedContentSize = CGSize(width: numItems * widthPlusGridLineWidth   gridLineWidth, height: numSections * heightPlusGridLineWidth   gridLineWidth) // Store computed content size
    }
    
    override var collectionViewContentSize: CGSize {
        return computedContentSize
    }
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var attributeList = [UICollectionViewLayoutAttributes]()
        
        for (_, attributes) in cellAttributes {
            if attributes.frame.intersects(rect) {
                attributeList.append(attributes)
            }
        }
        
        return attributeList
    }
    
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return cellAttributes[indexPath]
    }
    
}

Your controller class then becomes:

class OutlinedGridViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
    
    private let reuseIdentifier = "cell"
    
    @IBOutlet var collectionView: UICollectionView!
    
    var theData = [[String]]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // set view background to systemYellow so we can see the
        //  collection view frame
        view.backgroundColor = .systemYellow
        
        theData = [
            ["1", "Name", "TV", "LCD", "LED", "", ""],
            ["2", "Market", "No", "Charge", "Discount", "", ""],
            ["3", "value", "2.00", "05.00", "49.30", "200", ""],
            ["4", "Coupons", "1", "1","1","1","Total Price: "]]
        
        let layout = CustomLayout()
        collectionView.collectionViewLayout = layout
        collectionView.dataSource = self
        collectionView.delegate = self
        
        collectionView.contentInsetAdjustmentBehavior = .always
        collectionView.showsHorizontalScrollIndicator = false
        
        collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
        
        collectionView.backgroundColor = .black
    }

    
    // MARK: UICollectionViewDataSource
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return theData.count
    }
    
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return theData[0].count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCollectionViewCell
        
        let d = theData[indexPath.item]
        cell.myLabel.text = d[indexPath.section]

        // set background color for top row, else use white
        cell.contentView.backgroundColor = indexPath.section == 0 ? .yellow : .white

        return cell
    }

}

and, using this custom cell (a single, centered label):

class CustomCollectionViewCell: UICollectionViewCell {
    
    var myLabel = UILabel()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        myLabel.textColor = .black
        myLabel.textAlignment = .center
        myLabel.font = .systemFont(ofSize: 14.0)
        myLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(myLabel)
        
        let g = contentView
        NSLayoutConstraint.activate([
            
            myLabel.leadingAnchor.constraint(greaterThanOrEqualTo: g.leadingAnchor, constant: 4.0),
            myLabel.trailingAnchor.constraint(lessThanOrEqualTo: g.trailingAnchor, constant: -4.0),
            myLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            myLabel.centerYAnchor.constraint(equalTo: g.centerYAnchor),

        ])
    }
    
}

We get this (I made the collection view slightly smaller than needed, so we can see the horizontal and vertical scrolling):

enter image description here

enter image description here

You didn't explain what you want to do with the "Total Price" row... but if your intent is to have fewer columns on the last row, modifying this code will be a good exercise for you :)

CodePudding user response:

I couldn't really grasp the full understanding of how you do your calculations as it seems .minimumInteritemSpacing and .minimumLineSpacing don't seem to work as a normal UICollectionViewFlowLayout with horizontal scrolling direction should work.

For example, the documentation of .minimumLineSpacing

For a horizontally scrolling grid, this value represents the minimum spacing between successive columns.

But for you it is the spacing between rows. I think it might better to subclass UICollectionViewLayout instead of flow layout.

Anyways, as a solution you can do the following:

First as DonMag said, make your collection view a different color to your cell colors.

For example, make the collection view black and the cells white.

Since you, the .minimumLineSpacing seems to impact the spacing between rows so that will be like a horizontal line, you can set this to 1 or 2.

You will not see any difference when setting the minimumInteritemSpacing as this is the spacing between cells in the same section and items in your section is also laid out vertically.

Therefore you need to add spacing between the sections as each column of yours is a different section and you can do that with sectionInset

So after setting the collection view's background to orange and adding these lines to your layout configuration:

layout.minimumLineSpacing = 2
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 6)

You get a close enough solution although you will need to fix some of your calculations in your custom layout class to make it better and more accurate.

UICollectionView Custom layout spacing grid iOS Swift

  • Related