I have a collection view, I'd like to render the following layout
I tried to restrict the number of cells but I am unsure how to force the different count per row as you can see in the design
class ViewController: UIViewController {
private(set) lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: view.frame, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
collectionView.backgroundColor = .systemBackground
collectionView.dataSource = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "CustomCell")
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.backgroundColor = #colorLiteral(red: 0.1215686275, green: 0.1294117647, blue: 0.1490196078, alpha: 1)
collectionView.contentInset = .init(top: 24, left: 24, bottom: 24, right: 24)
view.addSubview(collectionView)
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return (5 * 5) + 6
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath)
cell.backgroundColor = #colorLiteral(red: 0.1647058824, green: 0.1764705882, blue: 0.2117647059, alpha: 1)
cell.layer.cornerRadius = cell.frame.width / 2
return cell
}
}
My attempt only renders
The colors aren't important only the layout.
If you can define the height / width for your collection view you can calculate the position
class ViewController: UIViewController {
private(set) lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .systemBackground
collectionView.dataSource = self
collectionView.delegate = self
collectionView.isScrollEnabled = false
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "CustomCell")
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.backgroundColor = #colorLiteral(red: 0.1215686275, green: 0.1294117647, blue: 0.1490196078, alpha: 1)
collectionView.contentInset = .init(top: 24, left: 24, bottom: 24, right: 24)
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.heightAnchor.constraint(equalToConstant: 340),
collectionView.widthAnchor.constraint(equalToConstant: 300),
collectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
collectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 48
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath)
switch indexPath.item {
case 0, 5, 42, 47: cell.backgroundColor = #colorLiteral(red: 0.1215686275, green: 0.1294117647, blue: 0.1490196078, alpha: 1)
default: cell.backgroundColor = #colorLiteral(red: 0.1647058824, green: 0.1764705882, blue: 0.2117647059, alpha: 1)
}
cell.layer.cornerRadius = cell.frame.width / 2
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: 28, height: 28)
}
}
Related
I've built a collection view programmatically which is working fine however I am trying to add a header but nothing is showing up at the top for the header.
Here are the lines of code I have included which I understand to be important:
collectionView.register(HeaderCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: HeaderCollectionReusableView.identifier)
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: HeaderCollectionReusableView.identifier, for: indexPath) as! HeaderCollectionReusableView
header.backgroundColor = .red
return header
}
func collectionView(_collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 100)
}
I am not sure if I am missing some important code or if something is in the wrong place?
Any help would be appreciated.
Thanks!
Declare collection view, UICollectionViewFlowLayout, cellId and headerId:
class YourController: UIViewController {
private var collectionView: UICollectionView?
let layout = UICollectionViewFlowLayout()
let cellId = "cellId"
let headerId = "headerId"
now in viewDidLoad set layout, collection and add constraints:
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: view.bounds.size.width / 2.2, height: view.bounds.size.width / 2.2) // set item size
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.backgroundColor = .clear
collectionView?.register(MyCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.register(MyHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerId)
collectionView?.contentInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
collectionView?.translatesAutoresizingMaskIntoConstraints = false
guard let collection = collectionView else { return }
view.addSubview(collection)
collection.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
collection.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
collection.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
collection.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
Now in a separate Extension (the code is much clean) conform the collection delegate and datasource:
extension YourController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MyCell
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerId, for: indexPath) as! MyHeader
header.configure()
return header
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 100)
}
}
After that set your custom cell:
class MyCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .link
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now set the reusable view for header:
class MyHeader: UICollectionReusableView {
private let label: UILabel = {
let label = UILabel()
label.text = "This is the header"
label.textAlignment = .center
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
public func configure() {
backgroundColor = .systemGreen
addSubview(label)
label.topAnchor.constraint(equalTo: topAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
}
This is the result:
I want to implement UITableView's UISwipeActionsConfiguration for a UICollectionView. In order to do so, I am using SwipeCellKit - github
My UICollectionView adopts to the SwipeCollectionViewCellDelegate protocol. And the cell inherits from SwipeCollectionViewCell.
ViewController with UICollectionView
class SwipeViewController: UIViewController {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(SwipeableCollectionViewCell.self, forCellWithReuseIdentifier: SwipeableCollectionViewCell.identifier)
collectionView.showsVerticalScrollIndicator = false
collectionView.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 4, right: 0)
collectionView.backgroundColor = UIColor(white: 0.97, alpha: 1)
collectionView.dataSource = self
collectionView.delegate = self
return collectionView
}()
var items: [String] = {
var items = [String]()
for i in 1 ..< 20 {
items.append("Item \(i)")
}
return items
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.setConstraints(topAnchor: view.topAnchor,
leadingAnchor: view.leadingAnchor,
bottomAnchor: view.bottomAnchor,
trailingAnchor: view.trailingAnchor,
leadingConstant: 10,
trailingConstant: 10)
}
}
extension SwipeViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 80)
}
}
extension SwipeViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SwipeableCollectionViewCell.identifier, for: indexPath) as! SwipeableCollectionViewCell
cell.backgroundColor = BackgroundColor.colors[indexPath.row]
cell.delegate = self
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
}
extension SwipeViewController: SwipeCollectionViewCellDelegate {
func collectionView(_ collectionView: UICollectionView, editActionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
guard orientation == .right else { return nil }
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
}
return [deleteAction]
}
}
SwipeCollectionViewCell
class SwipeableCollectionViewCell: SwipeCollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(nameLabel)
nameLabel.setConstraints(topAnchor: self.topAnchor,
leadingAnchor: self.leadingAnchor,
bottomAnchor: self.bottomAnchor,
trailingAnchor: self.trailingAnchor)
self.backgroundColor = .white
}
static let identifier = "TaskListTableViewCell"
private let nameLabel: UILabel = {
let label = UILabel()
label.text = "Simulator user has requested new graphics quality"
label.numberOfLines = 0
return label
}()
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
After doing so, when I swipe the cell, the deleteAction overlaps with the content of the cell.
As you see in the screenshot, the cell's content overlaps with the deleteAction text.
Update
setConstraints sets views's translatesAutoresizingMaskIntoConstraints to false
You must add nameLabel to contentView.
Change
self.addSubview(nameLabel)
nameLabel.setConstraints(topAnchor: self.topAnchor,
leadingAnchor: self.leadingAnchor,
bottomAnchor: self.bottomAnchor,
trailingAnchor: self.trailingAnchor)
to
self.contentView.addSubview(nameLabel)
nameLabel.setConstraints(topAnchor: self.contentView.topAnchor,
leadingAnchor: self.contentView.leadingAnchor,
bottomAnchor: self.contentView.bottomAnchor,
trailingAnchor: self.contentView.trailingAnchor)
Result:
Does anyone know what's happening here? I've configured a UITableView with CollectionViewCells, and each of these cells is supposed to operate independently of each other, yet when I swipe one it controls another cell which is 3 cells underneath it.
Tried to reconfigure my UITableViewCells and the CollectionViewCells. But I'm not exactly sure what to do
//TableView controler
import UIKit
import CardsLayout
import CHIPageControl
class eventsFeed: UIViewController, UITableViewDelegate, UITableViewDataSource{
// Global Variable
var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
tableView = UITableView(frame: CGRect(x: self.view.bounds.minX, y: self.view.bounds.minY+UIApplication.shared.statusBarFrame.height, width: self.view.bounds.width, height: self.view.bounds.height))
tableView.delegate = self
tableView.separatorStyle = .none
tableView.dataSource = self
tableView.showsVerticalScrollIndicator = false
self.view.addSubview(tableView)
tableView.register(TableViewCell.self, forCellReuseIdentifier: "TableViewCell")
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TableViewCell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.backgroundColor = UIColor.white
cell.selectionStyle = .none
return cell
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return 430.0;//Choose your custom row height
}
}
//CollectionViewCell for TableView
import UIKit
import CardsLayout
class TableViewCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate {
var collectionView: UICollectionView!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = UICollectionViewScrollDirection.horizontal
collectionView = UICollectionView(frame: CGRect(x: 0, y:0, width: UIScreen.main.bounds.width, height: 410.0), collectionViewLayout: layout)
collectionView.center = CGPoint(x: 187.5, y: 180)
collectionView.collectionViewLayout = CardsCollectionViewLayout()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.isPagingEnabled = true
collectionView.showsHorizontalScrollIndicator = false
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
collectionView.backgroundColor = UIColor.white
self.addSubview(collectionView)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
var colors: [UIColor] = [
UIColor(red: 237, green: 37, blue: 78),
UIColor(red: 249, green: 220, blue: 92),
UIColor.blue,
UIColor(red: 1, green: 25, blue: 54),
UIColor(red: 255, green: 184, blue: 209),
UIColor(red: 237, green: 37, blue: 78),
UIColor(red: 249, green: 220, blue: 92)
]
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath)
cell.layer.cornerRadius = 12.0
cell.backgroundColor = colors[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return colors.count
}
}
Video of issue: https://www.reddit.com/r/iOSProgramming/comments/btcok4/does_anyone_know_whats_happening_here_ive/
Cells are reused. That means that you have to choose different strategy of working with them.
Save somewhere current state for each table view cell (for example in structs in an array) and then configure cell based on this state in cellForRowAt delegate's method.
I have got this Swift code
let collectionView = UICollectionView(frame: CGRect(x:0,y:0,width:0,height:0),
collectionViewLayout: UICollectionViewFlowLayout())
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.register(PostCell.self, forCellWithReuseIdentifier: cellId)
collectionView.delegate = self
collectionView.dataSource = self
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! PostCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
{
return UIEdgeInsets(top: 15, left: 5, bottom: 15, right: 5)
}
My goal is to make cell's height automatic based on it's content.I wanted to implement
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
}
and
let layoutHeight = UICollectionViewFlowLayout()
layoutHeight.estimatedItemSize = CGSize(width: 100, height: 100)
collectionView = UICollectionView(frame: CGRect(x:0,y:0,width:0,height:0), collectionViewLayout: layoutHeight)
But the first one made all cells 50x50 and the second one made them 100x100.Here is my PostCell class
class PostCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
designCell()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let CellprofileImage: UIImageView = {
let v = UIImageView()
v.layer.cornerRadius = 55
v.layer.masksToBounds = true
v.layer.borderWidth = 3
v.layer.borderColor = UIColor(white: 0.5, alpha: 1).cgColor
v.image = #imageLiteral(resourceName: "guitar")
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
func designCell(){
addSubview(CellprofileImage)
backgroundColor = .red
cellConstraints()
}
func cellConstraints(){
CellprofileImage.topAnchor.constraint(equalTo: self.topAnchor,constant:20).isActive = true
CellprofileImage.leftAnchor.constraint(equalTo: self.leftAnchor,constant:20).isActive = true
CellprofileImage.widthAnchor.constraint(equalToConstant: 310).isActive = true
CellprofileImage.heightAnchor.constraint(equalToConstant: 310).isActive = true
}
}
I'm having strange issue while scrolling one of my collectionViews. When I'm printing indexPath.row is goes ok from 0..6 but then last indexPath.row is 3, and then if I scroll back it either crashes with excbadadress or prints 1, 2, 4 in random order. I assume that I have and error in my code but really don't understand where is it.
Here is my code:
VC:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
if collectionView == mainEventsCollectionView {
return CGSize(width: UIScreen.main.bounds.width - 40, height: 180)
} else {
return CGSize(width: 150, height: 200)
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == mainEventsCollectionView {
return 3
} else {
return 7
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == mainEventsCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mainEventsCell", for: indexPath) as! MainEventCollectionViewCell
cell.eventTitle.text = "Hello world".uppercased()
cell.setupCell()
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "importantEventsCell", for: indexPath) as! ImportantEventsCollectionViewCell
print(indexPath.row)
cell.setupCell()
return cell
}
}
Cell:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.roundCorners(.allCorners, radius: 6, borderColor: .clear, borderWidth: 0)
}
func setupCell() {
self.content.backgroundColor = colorWithAlpha(.black, alpha: 0.7)
self.eventDate.textColor = .white
self.eventTime.textColor = .white
self.eventName.textColor = .white
if self.content.layer.sublayers!.count > 3 {
self.content.layer.sublayers!.removeLast()
}
if self.eventDate.text == "Today" {
self.content.backgroundColor = .clear
DispatchQueue.main.async(execute: {
self.content.drawGradient(colors: [UIColor(red: 250/255, green: 217/255, blue: 97/255, alpha: 0.7).cgColor, UIColor(red: 255/255, green: 135/255, blue: 67/255, alpha: 0.7).cgColor], locations: [0, 1])
})
self.eventDate.textColor = .black
self.eventTime.textColor = .black
self.eventName.textColor = .black
}
}
Ok, the problem was that I've tried to apply gradient to a UIView, which had elements (UILables). What I had to do is add a subview to the UIView and apply gradient to it.