'How to make square cells with collection view layout in swift
I'm programming a game with a collection view with 121 buttons (11X11), how can I fix my Collection View cells to be a square? I want to increase or decrease the number of cells so the layout has to be dynamic
This is the code:
import UIKit
class GameViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
let reuseIdentifier="cell"
@IBOutlet var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.collectionViewLayout = generateLayout()
}
@objc
func animate(for sender:UIButton){
UIView.animate(withDuration: 0.5, delay: 0, animations: {
let rotate=CGAffineTransform(rotationAngle: .pi/2)
let scale=CGAffineTransform(scaleX: 0.5, y: 0.5)
sender.transform=rotate.concatenating(scale)
},completion: {_ in
UIView.animate(withDuration: 0.5, animations: {
sender.transform=CGAffineTransform.identity
})
})
}
func generateLayout()->UICollectionViewCompositionalLayout{
let padding:CGFloat=2
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(1)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets=NSDirectionalEdgeInsets(
top: 0, leading: padding, bottom: 0, trailing: padding
)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .absolute(40)
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: groupSize,
subitem: item,
count: 11
)
group.interItemSpacing = .fixed(padding)
group.contentInsets=NSDirectionalEdgeInsets(top: 0, leading: padding, bottom: 0, trailing: padding)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing=padding
section.contentInsets=NSDirectionalEdgeInsets(
top: padding,
leading: 0,
bottom: padding,
trailing: 0
)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 11
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return 11
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! ButtonCollectionViewCell
cell.layoutGridCells(at:indexPath)
cell.delegate=self
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = collectionView.bounds.size.height
return CGSize(width: size, height: size)
}
}
Solution 1:[1]
It can be cumbersome to create exact grids with a collection view.
And, as I mentioned in my comments, if you're not utilizing the built-in advantages of a UICollectionView -- scrolling, memory management via cell reuse, etc -- a collection view may not be the ideal approach.
Without knowing exactly what you need to do, buttons may not be the best to use either...
Here's a quick example using buttons in stack views:
class ButtonGridVC: UIViewController {
// vertical axis stack view to hold the "row" stack views
let outerStack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.distribution = .fillEqually
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let promptLabel = UILabel()
// spacing between buttons
let gridSpacing: CGFloat = 2.0
override func viewDidLoad() {
super.viewDidLoad()
// let's add a prompt label and a stepper
// for changing the grid size
let stepperStack = UIStackView()
stepperStack.spacing = 8
stepperStack.translatesAutoresizingMaskIntoConstraints = false
let stepper = UIStepper()
stepper.minimumValue = 2
stepper.maximumValue = 20
stepper.addTarget(self, action: #selector(stepperChanged(_:)), for: .valueChanged)
stepper.setContentCompressionResistancePriority(.required, for: .vertical)
stepperStack.addArrangedSubview(promptLabel)
stepperStack.addArrangedSubview(stepper)
view.addSubview(stepperStack)
view.addSubview(outerStack)
let g = view.safeAreaLayoutGuide
// these constraints at less-than-required priority
// will make teh outer stack view as large as will fit
let cw = outerStack.widthAnchor.constraint(equalTo: g.widthAnchor)
cw.priority = .required - 1
let ch = outerStack.heightAnchor.constraint(equalTo: g.heightAnchor)
ch.priority = .required - 1
NSLayoutConstraint.activate([
// prompt label and stepper at the top
stepperStack.topAnchor.constraint(greaterThanOrEqualTo: g.topAnchor, constant: 8.0),
stepperStack.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// constrain outerStack
// square (1:1 ratio)
outerStack.widthAnchor.constraint(equalTo: outerStack.heightAnchor),
// don't make it larger than availble space
outerStack.topAnchor.constraint(greaterThanOrEqualTo: stepperStack.bottomAnchor, constant: gridSpacing),
outerStack.leadingAnchor.constraint(greaterThanOrEqualTo: g.leadingAnchor, constant: gridSpacing),
outerStack.trailingAnchor.constraint(lessThanOrEqualTo: g.trailingAnchor, constant: -gridSpacing),
outerStack.bottomAnchor.constraint(lessThanOrEqualTo: g.bottomAnchor, constant: -gridSpacing),
// center horizontally and vertically
outerStack.centerXAnchor.constraint(equalTo: g.centerXAnchor),
outerStack.centerYAnchor.constraint(equalTo: g.centerYAnchor),
// active width/height constraints created above
cw, ch,
])
// spacing between buttons
outerStack.spacing = gridSpacing
// we'll start with an 11x11 grid
stepper.value = 11
makeGrid(11)
}
@objc func stepperChanged(_ stpr: UIStepper) {
// stepper changed, so generate new grid
makeGrid(Int(stpr.value))
}
func makeGrid(_ n: Int) {
// grid must be between 2x2 and 20x20
guard n < 21, n > 1 else {
print("Invalid grid size: \(n)")
return
}
// clear the existing buttons
outerStack.arrangedSubviews.forEach {
$0.removeFromSuperview()
}
// update the prompt label
promptLabel.text = "Grid Size: \(n)"
// for this example, we'll use a font size of 8 for a 20x20 grid
// adjusting it 1-pt larger for each smaller grid size
let font: UIFont = .systemFont(ofSize: CGFloat(8 + (20 - n)), weight: .light)
// generate grid of buttons
for _ in 0..<n {
// create a horizontal "row" stack view
let rowStack = UIStackView()
rowStack.spacing = gridSpacing
rowStack.distribution = .fillEqually
// add it to the outer stack view
outerStack.addArrangedSubview(rowStack)
// create buttons and add them to the row stack view
for _ in 0..<n {
let b = UIButton()
b.backgroundColor = .systemBlue
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.lightGray, for: .highlighted)
b.setTitle("X", for: [])
b.titleLabel?.font = font
b.addTarget(self, action: #selector(gotTap(_:)), for: .touchUpInside)
rowStack.addArrangedSubview(b)
}
}
}
@objc func gotTap(_ btn: UIButton) {
// if we want a "row, column" reference to the tapped button
if let rowStack = btn.superview as? UIStackView {
if let colIdx = rowStack.arrangedSubviews.firstIndex(of: btn),
let rowIdx = outerStack.arrangedSubviews.firstIndex(of: rowStack)
{
print("Tapped on row: \(rowIdx) column: \(colIdx)")
}
}
// animate the tapped button
UIView.animate(withDuration: 0.5, delay: 0, animations: {
let rotate = CGAffineTransform(rotationAngle: .pi/2)
let scale = CGAffineTransform(scaleX: 0.5, y: 0.5)
btn.transform = rotate.concatenating(scale)
}, completion: {_ in
UIView.animate(withDuration: 0.5, animations: {
btn.transform = CGAffineTransform.identity
})
})
}
}
The output:
Tapping on any button will animate it (using the rotation/scale code from your post), and will print the "Row" and "Column" of the tapped button in the debug console.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | DonMag |




