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