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?
I would like to show like below screenshot gridlines
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):
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.