iOS UICollectionView background around cell - ios

I have screen which look like:
As you see there is UICollectionView with image background. Background should be whiter than original image. But cells of collection view have to be transparent and display original part of image. Does somebody have idea how to it?
Update
Sample of background image
https://i.stack.imgur.com/h9Qp4.jpg

The basic idea will be:
add a background imageView behind the collectionView
configure your cells to have a translucent background with a transparent rectangular "hole"
configure the collectionView to exactly fit two columns with no spacing between the cells
handle variable "padding" on the cells so the outer portion matches the cell / row size
Here is an example:
class SeeThruCollectionViewCell: UICollectionViewCell {
static let identifier = "seeThruCellIdentifier"
var theLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .clear
v.textColor = .white
v.shadowColor = .black
v.shadowOffset = CGSize(width: 1, height: 1)
v.font = UIFont.boldSystemFont(ofSize: 17)
return v
}()
var overlayPath: UIBezierPath!
var transparentPath: UIBezierPath!
var fillLayer: CAShapeLayer!
var padding: CGRect!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
padding = CGRect.zero
// init and add the sublayer we'll use as a mask
fillLayer = CAShapeLayer()
layer.addSublayer(fillLayer)
// add a label
addSubview(theLabel)
// center the label
NSLayoutConstraint.activate([
theLabel.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0.0),
theLabel.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0.0),
])
}
override func layoutSubviews() {
super.layoutSubviews()
// overlayPath and transparentPath will combine to
// create a translucent mask with a transparent rect in the middle
overlayPath = UIBezierPath(rect: bounds)
var r = bounds
r.origin.x += padding.origin.x
r.origin.y += padding.origin.y
r.size.width -= r.origin.x + padding.size.width
r.size.height -= r.origin.y + padding.size.height
transparentPath = UIBezierPath(rect: r)
overlayPath.append(transparentPath)
overlayPath.usesEvenOddFillRule = true
fillLayer.path = overlayPath.cgPath
fillLayer.fillRule = kCAFillRuleEvenOdd
fillLayer.fillColor = UIColor(white: 1.0, alpha: 0.75).cgColor
}
}
class SeeThruViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var theCollectionView: UICollectionView = {
let v = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .clear
return v
}()
var theBackgroundImageView: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// translucent padding for cells
let pad = CGFloat(12)
let numItems = 10
let numSections = 1
override func viewDidLoad() {
super.viewDidLoad()
// add background image view
view.addSubview(theBackgroundImageView)
// add collection view view
view.addSubview(theCollectionView)
if let img = UIImage(named: "cvBKG") {
theBackgroundImageView.image = img
}
let guide = view.safeAreaLayoutGuide
// constrain background image view and collection view to same frames
NSLayoutConstraint.activate([
theBackgroundImageView.topAnchor.constraint(equalTo: guide.topAnchor, constant: 0.0),
theBackgroundImageView.bottomAnchor.constraint(equalTo: guide.bottomAnchor, constant: 0.0),
theBackgroundImageView.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: 0.0),
theBackgroundImageView.trailingAnchor.constraint(equalTo: guide.trailingAnchor, constant: 0.0),
theCollectionView.topAnchor.constraint(equalTo: theBackgroundImageView.topAnchor, constant: 0.0),
theCollectionView.bottomAnchor.constraint(equalTo: theBackgroundImageView.bottomAnchor, constant: 0.0),
theCollectionView.leadingAnchor.constraint(equalTo: theBackgroundImageView.leadingAnchor, constant: 0.0),
theCollectionView.trailingAnchor.constraint(equalTo: theBackgroundImageView.trailingAnchor, constant: 0.0),
])
// normal collection view tasks
theCollectionView.register(SeeThruCollectionViewCell.self, forCellWithReuseIdentifier: SeeThruCollectionViewCell.identifier)
theCollectionView.dataSource = self
theCollectionView.delegate = self
// we want ZERO spacing between the cells
if let flow = theCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flow.minimumLineSpacing = 0
flow.minimumInteritemSpacing = 0
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// set cell size to fill exactly two "columns"
if let flow = theCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flow.itemSize = CGSize(width: theCollectionView.frame.size.width / 2.0, height: theCollectionView.frame.size.width / 2.0)
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return numSections
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return numItems
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SeeThruCollectionViewCell.identifier, for: indexPath) as! SeeThruCollectionViewCell
cell.theLabel.text = "Cell: \(indexPath.item)"
var padRect = CGRect(x: pad, y: pad, width: pad, height: pad)
if indexPath.item <= 1 {
// if it's the first row
// add paddingn on the top
padRect.origin.y = pad * 2
}
if indexPath.item % 2 == 0 {
// if it's the left "column"
// add padding on the left
padRect.origin.x = pad * 2
} else {
// if it's the right "column"
// add padding on the right
padRect.size.width = pad * 2
}
if indexPath.item / 2 >= (numItems / 2) - 1 {
// if it's the last row
// add paddingn on the bottom
padRect.size.height = pad * 2
}
cell.padding = padRect
return cell
}
}
Name your background image cvNKG.png and add it to your project's Assets, then create a new UIViewController and assign its class to SeeThruViewController. You should be able to run this as-is.
Result:
Note: this is a starting point. You'll (obviously) need to add labels for your needs, and you'll want to handle cases such as an odd number of items or if the number of rows don't fill the screen.

Related

How can I change the constraints so that it was like on the second screenshot?

Please can you help me to change constraints in code to take an elements like in the second screen.
enter image description here
enter image description here
class DetailsHomeViewController: UIViewController {
var images:[String] = ["label","label","label"]
let MainImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.image = UIImage(named: "label.png")
theImageView.translatesAutoresizingMaskIntoConstraints = false
return theImageView
}()
let someImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.backgroundColor = .white
theImageView.translatesAutoresizingMaskIntoConstraints = false
theImageView.isUserInteractionEnabled = true
return theImageView
}()
lazy var collectionView:UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.dataSource = self
cv.delegate = self
cv.register(ImageCell.self, forCellWithReuseIdentifier: ImageCell.identifier)
cv.translatesAutoresizingMaskIntoConstraints = false
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(MainImageView)
view.addSubview(someImageView)
someImageView.addSubview(collectionView)
someImageViewConstraints()
MainImageViewConstraints()
view.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
setupViews()
mysetupViews()
}
private func setupViews() {
someImageViewConstraints()
}
func someImageViewConstraints() {
someImageView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width/2)
someImageView.center = view.center
let itemHeight = 120
let padding = 25
let width = (itemHeight * 3) + padding
collectionView.frame = CGRect(x: Int(view.center.x) - (width / 2),
y: Int(someImageView.frame.height) - (itemHeight + padding),
width: width, height: itemHeight)
}
private func mysetupViews() {
createCustomNavigationBar()
let RightButton = createCustomButton(
imageName: "square.and.arrow.up",
selector: #selector(RightButtonTapped)
)
let customTitleView = createCustomTitleView(
detailsName: "Label"
)
navigationItem.rightBarButtonItems = [RightButton]
navigationItem.titleView = customTitleView
}
#objc private func RightButtonTapped() {
print("RightButtonTapped")
}
func MainImageViewConstraints() {
MainImageView.translatesAutoresizingMaskIntoConstraints = false
} }
extension DetailsHomeViewController:UICollectionViewDataSource , UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImageCell.identifier, for: indexPath) as! ImageCell
cell.someImageView.image = UIImage(named: images[indexPath.row])
return cell
}
} extension DetailsHomeViewController:UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.height, height: collectionView.frame.height)
}
}
class ImageCell:UICollectionViewCell {
static let identifier = "ImageCell"
override var isSelected: Bool {
didSet {
self.someImageView.layer.borderColor = isSelected ? UIColor.green.cgColor : UIColor.clear.cgColor
self.someImageView.layer.borderWidth = 5
}
}
let someImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.clipsToBounds = true
return theImageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.cornerRadius = 8
self.clipsToBounds = true
addSubview(someImageView)
someImageView.frame = self.bounds
}
override func layoutSubviews() {
super.layoutSubviews()
//someImageView.frame = self.frame
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You really need to go through a bunch of good auto-layout tutorials, but here is a quick general idea...
We'll constrain the "main image view" 12-points from the top, 40-points from each side, and give it a 5:3 ratio.
Then we'll add a UIStackView (with 12-point spacing) below it, constrained 12-points from the bottom of the main image view, 75% of the width, centered horizontally.
Each image view that we add to the stack view will be constrained to a 1:2 ratio (square).
Here's sample code to do that:
class DetailsHomeViewController: UIViewController {
var images:[String] = ["label", "label", "label"]
let mainImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.contentMode = .scaleAspectFill
theImageView.clipsToBounds = true
theImageView.layer.cornerRadius = 24.0
return theImageView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
setupViews()
mysetupViews()
}
private func setupViews() {
// a stack view to hold the "thumbnail" image views
let stackView = UIStackView()
stackView.spacing = 12
mainImageView.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mainImageView)
view.addSubview(stackView)
// respect the safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// main image view - let's go with
// Top with 12-points "padding"
mainImageView.topAnchor.constraint(equalTo: g.topAnchor, constant: 12.0),
// Leading and Trailing with 40-points "padding"
mainImageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
mainImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
// height equal to width x 3/5ths (5:3 aspect ratio)
mainImageView.heightAnchor.constraint(equalTo: mainImageView.widthAnchor, multiplier: 3.0 / 5.0),
// stack view
// Top 12-points from main image view Bottom
stackView.topAnchor.constraint(equalTo: mainImageView.bottomAnchor, constant: 12.0),
// width equal to 75% of the main image view width
stackView.widthAnchor.constraint(equalTo: mainImageView.widthAnchor, multiplier: 0.75),
// centered horizontally
stackView.centerXAnchor.constraint(equalTo: mainImageView.centerXAnchor),
])
// make sure we can load the main image
if let img = UIImage(named: images[0]) {
mainImageView.image = img
}
// now we'll add 3 image views to the stack view
images.forEach { imgName in
let thumbView = UIImageView()
thumbView.contentMode = .scaleAspectFill
thumbView.clipsToBounds = true
thumbView.layer.cornerRadius = 16.0
// we want them to be square
thumbView.heightAnchor.constraint(equalTo: thumbView.widthAnchor).isActive = true
// make sure we can load the image
if let img = UIImage(named: imgName) {
thumbView.image = img
}
stackView.addArrangedSubview(thumbView)
}
}
private func mysetupViews() {
createCustomNavigationBar()
let RightButton = createCustomButton(
imageName: "square.and.arrow.up",
selector: #selector(RightButtonTapped)
)
let customTitleView = createCustomTitleView(
detailsName: "Label"
)
navigationItem.rightBarButtonItems = [RightButton]
navigationItem.titleView = customTitleView
}
#objc private func RightButtonTapped() {
print("RightButtonTapped")
}
}

Max width for content view of UICollectionView

A common pattern in UI is to maximize the size of the view up to some point and after that fill the rest of its superview with the spaces.
When using AutoLayout, it can be achieved easily with width <= X constraint. But when using this with UICollectionView, the scroll area matches the size of UICollectionView, so the sides are unscrollable which is unwanted for me.
So, the only way I found to achieve the behavior is to use the proper layout inside the cells themselves. I consider this as a not very good design decision (especially when you have multiple cells). But are there any alternatives available?
We can accomplish this by subclassing UICollectionView and implementing hitTest(_:with:).
What we'll do is extend the "touch area" wider than the collection view itself:
class ExtendedCollectionView: UICollectionView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.bounds.contains(point) {
// touch is inside self.bounds, so
// send it on to super (i.e. "normal" behavior)
// so we can select a cell on tap
return super.hitTest(point, with: event)
}
if self.bounds.insetBy(dx: -self.frame.origin.x, dy: 0).contains(point) {
// touch is outside self.bounds, but
// it IS inside bounds extended left and right, so
// capture the touch for self
return self
}
// touch was outside self.bounds (and outside our extended bounds), so
// send it on to super (i.e. "normal" behavior)
// so the rest of the view hierarchy (buttons, etc)
// can receive the gesture
return super.hitTest(point, with: event)
}
}
You can now use ExtendedCollectionView just as you would use UICollectionView, except you'll be able to scroll it starting from left or right, outside of its bounds.
Here's a complete example:
class ViewController: UIViewController {
var myData: [String] = []
// we'll use these colors for the cell backgrounds
let colors: [UIColor] = [
.systemRed, .systemGreen, .systemBlue,
.systemPink, .systemYellow, .systemTeal,
]
// our "extended collection view"
var collectionView: ExtendedCollectionView!
let cellSize: CGSize = CGSize(width: 80, height: 100)
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Extended CollectionView"
// always respect the safe area
let g = view.safeAreaLayoutGuide
// let's add a "background" image view, sized to fit the view
// so we can easily see the reults
if let img = UIImage(named: "sampleBKG") {
let v = UIImageView()
v.image = img
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
view.sendSubviewToBack(v)
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
v.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
v.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
}
// fill myData array with 20 strings
// of different lengths to show this
// works with dynamic width cells
let strs: [String] = [
"Short",
"Bit Longer",
"Much Longer String",
]
for i in 0..<20 {
myData.append("C: \(i) \(strs[i % strs.count])")
}
// set the flow layout properties
let fl = UICollectionViewFlowLayout()
fl.estimatedItemSize = CGSize(width: 50, height: 100)
fl.scrollDirection = .horizontal
fl.minimumLineSpacing = 8
fl.minimumInteritemSpacing = 8
// create an instance of ExtendedCollectionView
collectionView = ExtendedCollectionView(frame: .zero, collectionViewLayout: fl)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .clear
view.addSubview(collectionView)
// let's make the collection view
// 80% of the width of the view's safe area
let cvWidthPercent = 0.8
// let's add a label below our custom view
// the same percentage width, so we can
// easily see the layout
let v = UILabel()
v.backgroundColor = .green
v.translatesAutoresizingMaskIntoConstraints = false
v.textAlignment = .center
v.text = "\(cvWidthPercent * 100)%"
view.addSubview(v)
NSLayoutConstraint.activate([
// let's put our collection view
// 80-pts from the top
collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 80.0),
// centered Horizontally
collectionView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// height equal to cell Height
collectionView.heightAnchor.constraint(equalToConstant: cellSize.height),
// 80% of the width of the safe area
collectionView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: cvWidthPercent),
// constrain label 8-pts below the collection view
v.topAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 8.0),
// centered Horizontally
v.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// same percentage width
v.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: cvWidthPercent),
])
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(MyDynamicCVCell.self, forCellWithReuseIdentifier: "cvCell")
}
}
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return myData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cvCell", for: indexPath) as! MyDynamicCVCell
cell.contentView.backgroundColor = colors[indexPath.item % colors.count]
cell.label.text = myData[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Did Select Cell At:", indexPath)
}
}
a simple dynamic-width cell
class MyDynamicCVCell: UICollectionViewCell {
let label: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.textAlignment = .center
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
label.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
])
}
}
The result looks like this:
When you run it, you'll see that you can scroll horizontally, even if you start dragging from the left or right of the cells.

Attempt to achieve fixed height, dynamic width (wrap content) horizontal UICollectionView's cell by using UICollectionViewCompositionalLayout

In https://stackoverflow.com/a/51231881/72437, it show how to achieve a full width, dynamic height in vertical UICollectionView's cell, by using UICollectionViewCompositionalLayout
We would like to achieve the same on a horizontal UICollectionView, with requirements
Fixed height 44
Minimum width 44
The width should be dynamically enlarge, when the content grows
Here's how our solution looks like
class MenuTabsView: UIView, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
lazy var collView: UICollectionView = {
let itemSize = NSCollectionLayoutSize(
widthDimension: NSCollectionLayoutDimension.estimated(44),
heightDimension: NSCollectionLayoutDimension.fractionalHeight(1.0)
)
let item = NSCollectionLayoutItem(
layoutSize: itemSize
)
item.contentInsets = NSDirectionalEdgeInsets(
top: 0,
leading: 0,
bottom: 0,
trailing: 1
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: itemSize,
subitem: item,
count: 1
)
let section = NSCollectionLayoutSection(group: group)
let configuration = UICollectionViewCompositionalLayoutConfiguration()
configuration.scrollDirection = .horizontal
let layout = UICollectionViewCompositionalLayout(section: section, configuration: configuration)
let cv = UICollectionView.init(frame: CGRect.zero, collectionViewLayout: layout)
cv.showsHorizontalScrollIndicator = false
cv.backgroundColor = .white
cv.delegate = self
cv.dataSource = self
return cv
}()
We expect by using widthDimension: NSCollectionLayoutDimension.estimated(44), that's the key to make the cell width grow dynamically. However, that doesn't work as expected. It looks like
May I know, how can we solve this problem by using UICollectionViewCompositionalLayout ? The complete workable project is located at https://github.com/yccheok/PageViewControllerWithTabs/tree/UICollectionViewCompositionalLayout
p/s
We want to avoid from using UICollectionViewDelegateFlowLayout method collectionView(_:layout:sizeForItemAt:). As, our cell can grow complex, and it will contain other views besides UILabel. Having to calculate the content size manually, will make the solution inflexible and error prone.
I have created what you need. Follow the following steps:
Create a cell with a label inside.
Add trailing and leading constraints to the label (my example has 10 and 10) (important)
Set the label to align vertically.
Set the width of the label to be >= 44. You can do this by inspecting the constraint and changing from equal to >=.
Once this is done you need to create your layout. I had the following function (I have height 50 just change to 44 in your case):
func layoutConfig() -> UICollectionViewCompositionalLayout {
return UICollectionViewCompositionalLayout { (sectionNumber, env) -> NSCollectionLayoutSection? in
let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(44), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .estimated(44), heightDimension: .absolute(50))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
return section
}
}
Then in your viewDidLoad() function before you call the delegate or dataSource methods you need to call the following (assuming you keep the same function name):
collectionView.collectionViewLayout = layoutConfig()
You should end up with the following. It scrolls horizontally:
Here is the source code to the example:
https://github.com/williamfinn/HorizontalCollectionExample
You seem to be asking the same question over and over?
You may find it helpful if you ask your complete question first.
You say "our cell content can grow complex" but you don't provide any information about what "complex" might be.
Here's an example that might get you headed in the right direction.
First, the output... Each "tab" has an Image View, a Label and a Button, or a combination of elements as follows:
// 1st tab is Image + Label + Button
// 2nd tab is Label + Button
// 3rd tab is Image + Label
// 4th tab is Image + Button
// 5th tab is Label Only
// 6th tab is Button Only
// 7th tab is Image Only
With different color tabs:
With the same color tabs, except for the "active" tab:
With background colors on the tab elements to see the frames:
I used this as the struct for the tab info:
struct TabInfo {
var name: String? = ""
var color: Int = 0
var imageName: String? = ""
var buttonTitle: String? = ""
}
and I used this from your GitHub repo:
class Utils {
static func intToUIColor(argbValue: Int) -> UIColor {
// & binary AND operator to zero out other color values
// >> bitwise right shift operator
// Divide by 0xFF because UIColor takes CGFloats between 0.0 and 1.0
let red = CGFloat((argbValue & 0xFF0000) >> 16) / 0xFF
let green = CGFloat((argbValue & 0x00FF00) >> 8) / 0xFF
let blue = CGFloat(argbValue & 0x0000FF) / 0xFF
let alpha = CGFloat((argbValue & 0xFF000000) >> 24) / 0xFF
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
}
Sample code for the view controller:
class TabsTestViewController: UIViewController {
// 1st tab is Image + Label + Button
// 2nd tab is Label + Button
// 3rd tab is Image + Label
// 4th tab is Image + Button
// 5th tab is Label Only
// 6th tab is Button Only
// 7th tab is Image Only
var tabs = [
TabInfo(name: "All", color: 0xff5481e6, imageName: "swiftBlue64x64", buttonTitle: "One"),
TabInfo(name: "Calendar", color: 0xff7cb342, imageName: nil, buttonTitle: "Two"),
TabInfo(name: "Home", color: 0xffe53935, imageName: "swiftBlue64x64", buttonTitle: nil),
TabInfo(name: nil, color: 0xfffb8c00, imageName: "swiftBlue64x64", buttonTitle: "Work"),
TabInfo(name: "Label Only", color: 0xffe00000, imageName: nil, buttonTitle: nil),
TabInfo(name: nil, color: 0xff008000, imageName: nil, buttonTitle: "Button Only"),
TabInfo(name: nil, color: 0xff000080, imageName: "swiftBlue64x64", buttonTitle: nil),
]
let menuTabsView: MenuTabsView = {
let v = MenuTabsView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let otherView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(white: 0.8, alpha: 1.0)
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(menuTabsView)
view.addSubview(otherView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain menuTabsView Top / Leading / Trailing
menuTabsView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
menuTabsView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
menuTabsView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
// constrain otherView Leading / Trailing / Bottom
otherView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
otherView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
otherView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
// constrain otherView Top to menuTabsView Bottom
otherView.topAnchor.constraint(equalTo: menuTabsView.bottomAnchor, constant: 0.0),
])
// un-comment to set all tab colors to green - 0xff7cb342
// except first tab
//for i in 1..<tabs.count {
// tabs[i].color = 0xff7cb342
//}
menuTabsView.dataArray = tabs
// set background color of "bottom bar" to first tab's background color
guard let tab = tabs.first else {
return
}
menuTabsView.bottomBar.backgroundColor = Utils.intToUIColor(argbValue: tab.color)
}
}
Sample code for the MenuTabsView:
class MenuTabsView: UIView {
var tabsHeight: CGFloat = 44
var dataArray: [TabInfo] = [] {
didSet{
self.collView.reloadData()
}
}
lazy var collView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 1
// needed to prevent last cell being clipped
layout.minimumInteritemSpacing = 1
layout.estimatedItemSize = CGSize(width: 100, height: self.tabsHeight)
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
return cv
}()
let bottomBar: UIView = {
let v = UIView()
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
collView.translatesAutoresizingMaskIntoConstraints = false
bottomBar.translatesAutoresizingMaskIntoConstraints = false
addSubview(collView)
addSubview(bottomBar)
NSLayoutConstraint.activate([
// collection view constrained Top / Leading / Trailing
collView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
collView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
collView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// collection view Height constrained to 44
collView.heightAnchor.constraint(equalToConstant: tabsHeight),
// "bottom bar" constrained Leading / Trailing / Bottom
bottomBar.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
bottomBar.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
bottomBar.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
// "bottom bar" Height constrained to 4-pts
bottomBar.heightAnchor.constraint(equalToConstant: 4.0),
// collection view Bottom constrained to "bottom bar" Top
collView.bottomAnchor.constraint(equalTo: bottomBar.topAnchor),
])
collView.register(MyStackCell.self, forCellWithReuseIdentifier: "cell")
collView.dataSource = self
collView.delegate = self
collView.backgroundColor = .clear
backgroundColor = .white
}
}
extension MenuTabsView: UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyStackCell
let t = dataArray[indexPath.item]
cell.configure(with: t.name, imageName: t.imageName, buttonTitle: t.buttonTitle, bkgColor: t.color)
//cell.configure(with: t.name, or: nil, or: "My Button")
return cell
}
}
and finally, the collection view cell:
class MyStackCell: UICollectionViewCell {
// can be set by caller to change default cell height
public var stackHeight: CGFloat = 36.0
private var stackHeightConstraint: NSLayoutConstraint!
private let label: UILabel = {
let v = UILabel()
v.textAlignment = .center
return v
}()
private let imageView: UIImageView = {
let v = UIImageView()
return v
}()
private let button: UIButton = {
let v = UIButton()
v.setTitleColor(.white, for: .normal)
v.setTitleColor(.lightGray, for: .highlighted)
return v
}()
private let stack: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.alignment = .center
v.spacing = 8
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.addSubview(stack)
// stack views in cells get cranky
// so we set priorities on desired element constraints to 999 (1 less than required)
// to avoid console warnings
var c = imageView.heightAnchor.constraint(equalToConstant: 32.0)
c.priority = UILayoutPriority(rawValue: 999)
c.isActive = true
// image view has 1:1 ratio
c = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
c.priority = UILayoutPriority(rawValue: 999)
c.isActive = true
// minimum width for label if desired
c = label.widthAnchor.constraint(greaterThanOrEqualToConstant: 44.0)
c.priority = UILayoutPriority(rawValue: 999)
c.isActive = true
// minimum width for button if desired
c = button.widthAnchor.constraint(greaterThanOrEqualToConstant: 44.0)
c.priority = UILayoutPriority(rawValue: 999)
c.isActive = true
// height for stack view
stackHeightConstraint = stack.heightAnchor.constraint(equalToConstant: stackHeight)
stackHeightConstraint.priority = UILayoutPriority(rawValue: 999)
stackHeightConstraint.isActive = true
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4.0),
stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0),
stack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0),
stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4.0),
])
stack.addArrangedSubview(imageView)
stack.addArrangedSubview(label)
stack.addArrangedSubview(button)
// during development, so we can see the frames
// delete or comment-out when satisfied with layout
//imageView.backgroundColor = .yellow
//label.backgroundColor = .green
//button.backgroundColor = .blue
}
func customTabHeight(_ h: CGFloat) -> Void {
stackHeightConstraint.constant = h
}
func configure(with name: String?, imageName: String?, buttonTitle: String?, bkgColor: Int?) -> Void {
// set and show elements
// or hide if nil
if let s = imageName, s.count > 0 {
if let img = UIImage(named: s) {
imageView.image = img
imageView.isHidden = false
}
} else {
imageView.isHidden = true
}
if let s = name, s.count > 0 {
label.text = s
label.isHidden = false
} else {
label.isHidden = true
}
if let s = buttonTitle, s.count > 0 {
button.setTitle(s, for: [])
button.isHidden = false
} else {
button.isHidden = true
}
if let c = bkgColor {
backgroundColor = Utils.intToUIColor(argbValue: c)
}
}
override func layoutSubviews() {
super.layoutSubviews()
// set the mask in layoutSubviews
let maskPath = UIBezierPath(roundedRect: bounds,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: 12.0, height: 12.0))
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
layer.mask = shape
}
}
Note that this is Example Code Only!!!
It is not intended to be production-ready --- it's just to help you on your way.
Implement UICollectionViewDelegateFlowLayout method collectionView(_:layout:sizeForItemAt:) method to return the cell size according to your text.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let text = "This is your text"
let size = text.size(withAttributes:[.font: UIFont.systemFont(ofSize:18.0)])
return CGSize(width: size.width + 10.0, height: 44.0)
}

Swift add items in scrollable list

right now this is all I have in my project:
In the end it should look and function pretty like this:
1. How do I add items into the ScrollView (in a 2 x X View)
2. How do I make the ScrollView actually be able to scroll (and refresh like in the 3 pictures below) or is this maybe solvable with just a list?
UPDATE
The final view should look like this:
The "MainWishList" cell and the "neue Liste erstellen" (= add new cell) should be there from the beginning. When the user clicks the "add-Cell" he should be able to choose a name and image for the list.
Part of the built-in functionality of a UICollectionView is automatic scrolling when you have more items (cells) than will fit in the frame. So there is no need to embed a collection view in a scroll view.
Here is a basic example. Everything is done via code (no #IBOutlet, #IBAction or prototype cells). Create a new UIViewController and assign its class to ExampleViewController as found below:
//
// ExampleViewController.swift
// CollectionAddItem
//
// Created by Don Mag on 10/22/19.
//
import UIKit
// simple cell with label
class ContentCell: UICollectionViewCell {
let theLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.textAlignment = .center
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.backgroundColor = .yellow
contentView.addSubview(theLabel)
// constrain label to all 4 sides
NSLayoutConstraint.activate([
theLabel.topAnchor.constraint(equalTo: contentView.topAnchor),
theLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
theLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
theLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
}
}
// simple cell with button
class AddItemCell: UICollectionViewCell {
let btn: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("+", for: .normal)
v.setTitleColor(.systemBlue, for: .normal)
v.titleLabel?.font = UIFont.systemFont(ofSize: 40.0)
return v
}()
// this will be used as a "callback closure" in collection view controller
var tapCallback: (() -> ())?
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.backgroundColor = .green
contentView.addSubview(btn)
// constrain button to all 4 sides
NSLayoutConstraint.activate([
btn.topAnchor.constraint(equalTo: contentView.topAnchor),
btn.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
btn.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
btn.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
btn.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
}
#objc func didTap(_ sender: Any) {
// tell the collection view controller we got a button tap
tapCallback?()
}
}
class ExampleViewController: UIViewController, UICollectionViewDataSource {
let theCollectionView: UICollectionView = {
let v = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white
v.contentInsetAdjustmentBehavior = .always
return v
}()
let columnLayout = FlowLayout(
itemSize: CGSize(width: 100, height: 100),
minimumInteritemSpacing: 10,
minimumLineSpacing: 10,
sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
)
// track collection view frame change
var colViewWidth: CGFloat = 0.0
// example data --- this will be filled with simple number strings
var theData: [String] = [String]()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
view.addSubview(theCollectionView)
// constrain collection view
// 100-pts from top
// 60-pts from bottom
// 40-pts from leading
// 40-pts from trailing
NSLayoutConstraint.activate([
theCollectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100.0),
theCollectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -60.0),
theCollectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 40.0),
theCollectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -40.0),
])
// register the two cell classes for reuse
theCollectionView.register(ContentCell.self, forCellWithReuseIdentifier: "ContentCell")
theCollectionView.register(AddItemCell.self, forCellWithReuseIdentifier: "AddItemCell")
// set collection view dataSource
theCollectionView.dataSource = self
// use custom flow layout
theCollectionView.collectionViewLayout = columnLayout
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// only want to call this when collection view frame changes
// to set the item size
if theCollectionView.frame.width != colViewWidth {
let w = theCollectionView.frame.width / 2 - 15
columnLayout.itemSize = CGSize(width: w, height: w)
colViewWidth = theCollectionView.frame.width
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// return 1 more than our data array (the extra one will be the "add item" cell
return theData.count + 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// if item is less that data count, return a "Content" cell
if indexPath.item < theData.count {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ContentCell", for: indexPath) as! ContentCell
cell.theLabel.text = theData[indexPath.item]
return cell
}
// past the end of the data count, so return an "Add Item" cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AddItemCell", for: indexPath) as! AddItemCell
// set the closure
cell.tapCallback = {
// add item button was tapped, so append an item to the data array
self.theData.append("\(self.theData.count + 1)")
// reload the collection view
collectionView.reloadData()
collectionView.performBatchUpdates(nil, completion: {
(result) in
// scroll to make newly added row visible (if needed)
let i = collectionView.numberOfItems(inSection: 0) - 1
let idx = IndexPath(item: i, section: 0)
collectionView.scrollToItem(at: idx, at: .bottom, animated: true)
})
}
return cell
}
}
// custom FlowLayout class to left-align collection view cells
// found here: https://stackoverflow.com/a/49717759/6257435
class FlowLayout: UICollectionViewFlowLayout {
required init(itemSize: CGSize, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
super.init()
self.itemSize = itemSize
self.minimumInteritemSpacing = minimumInteritemSpacing
self.minimumLineSpacing = minimumLineSpacing
self.sectionInset = sectionInset
sectionInsetReference = .fromSafeArea
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
guard scrollDirection == .vertical else { return layoutAttributes }
// Filter attributes to compute only cell attributes
let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell })
// Group cell attributes by row (cells with same vertical center) and loop on those groups
for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) {
// Set the initial left inset
var leftInset = sectionInset.left
// Loop on cells to adjust each cell's origin and prepare leftInset for the next cell
for attribute in attributes {
attribute.frame.origin.x = leftInset
leftInset = attribute.frame.maxX + minimumInteritemSpacing
}
}
return layoutAttributes
}
}
When you run this, the data array will be empty, so the first thing you'll see is:
Each time you tap the "+" cell, a new item will be added to the data array (in this example, a numeric string), reloadData() will be called, and a new cell will appear.
Once we have enough items in our data array so they won't all fit in the collection view frame, the collection view will become scrollable:

How can I change my UICollectionView's Flow Layout to a vertical List with Horizontal Scrolling

Basically what I am trying to create is a table with three cells stacked on top of one another. But, if there are more than three cells, I want to be able to swipe left on the Collection View to show more cells. Here is a picture to illustrate.
Right now I have the cells arranged in a list but I cannot seem to change the scroll direction for some reason. - They still scroll vertically
Here is my current code for the Flow Layout:
Note: I'm not going to include the Collection View code that is in my view controller as I do not think it is relevant.
import Foundation
import UIKit
class HorizontalListCollectionViewFlowLayout: UICollectionViewFlowLayout {
let itemHeight: CGFloat = 35
func itemWidth() -> CGFloat {
return collectionView!.frame.width
}
override var itemSize: CGSize {
set {
self.itemSize = CGSize(width: itemWidth(), height: itemHeight)
}
get {
return CGSize(width: itemWidth(), height: itemHeight)
}
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
return collectionView!.contentOffset
}
override var scrollDirection: UICollectionViewScrollDirection {
set {
self.scrollDirection = .horizontal
} get {
return self.scrollDirection
}
}
}
If you have your cells sized correctly, Horizontal Flow Layout will do exactly what you want... fill down and across.
Here is a simple example (just set a view controller to this class - no IBOutlets needed):
//
// ThreeRowCViewViewController.swift
//
// Created by Don Mag on 6/20/17.
//
import UIKit
private let reuseIdentifier = "LabelItemCell"
class LabelItemCell: UICollectionViewCell {
// simple CollectionViewCell with a label
#IBOutlet weak var theLabel: UILabel!
let testLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.textColor = UIColor.black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addViews()
}
func addViews(){
addSubview(testLabel)
testLabel.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8.0).isActive = true
testLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0.0).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ThreeRowCViewViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
// 3 gray colors for the rows
let cellColors = [
UIColor.init(white: 0.9, alpha: 1.0),
UIColor.init(white: 0.8, alpha: 1.0),
UIColor.init(white: 0.7, alpha: 1.0)
]
var theCodeCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// height we'll use for the rows
let rowHeight = 30
// just picked a random width of 240
let rowWidth = 240
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
// horizontal collection view direction
layout.scrollDirection = .horizontal
// each cell will be the width of the collection view and our pre-defined height
layout.itemSize = CGSize(width: rowWidth - 1, height: rowHeight)
// no item spacing
layout.minimumInteritemSpacing = 0.0
// 1-pt line spacing so we have a visual "edge" (with horizontal layout, the "lines" are vertical blocks of cells
layout.minimumLineSpacing = 1.0
theCodeCollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
theCodeCollectionView.dataSource = self
theCodeCollectionView.delegate = self
theCodeCollectionView.register(LabelItemCell.self, forCellWithReuseIdentifier: reuseIdentifier)
theCodeCollectionView.showsVerticalScrollIndicator = false
// set background to orange, just to make it obvious
theCodeCollectionView.backgroundColor = .orange
theCodeCollectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(theCodeCollectionView)
// set collection view width x height to rowWidth x (rowHeight * 3)
theCodeCollectionView.widthAnchor.constraint(equalToConstant: CGFloat(rowWidth)).isActive = true
theCodeCollectionView.heightAnchor.constraint(equalToConstant: CGFloat(rowHeight * 3)).isActive = true
// center the collection view
theCodeCollectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0).isActive = true
theCodeCollectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0).isActive = true
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! LabelItemCell
cell.backgroundColor = cellColors[indexPath.row % 3]
cell.testLabel.text = "\(indexPath)"
return cell
}
}
I'll leave the "enable paging" part up to you :)

Resources