iosswiftiphonecollectionviewuicollectionviewflowlayout

How to show grid lines in Collecionview Swift


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


Solution

  • 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 :)