Nested collection views with dynamic content - ios

I am using two nested collection views. I have added the ChildCollectionView to the ParentCollectionViewCell, and ChildCollectionView have 3 cells in it but the ParentCollectionViewCell does not adjust the frame of the cell as per the content.
Here's the code,
ParentCollectionView
class ViewController: UIViewController {
var parentCollectionView: UICollectionView = {
let _collectionView = UICollectionView.init(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout.init())
return _collectionView
}()
let id = "ParentCollectionViewCell"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(parentCollectionView)
parentCollectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
parentCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
parentCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
parentCollectionView.topAnchor.constraint(equalTo: view.topAnchor),
parentCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
parentCollectionView.dataSource = self
parentCollectionView.register(UINib(nibName: id, bundle: nil), forCellWithReuseIdentifier: id)
if let flowLayout = parentCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: id, for: indexPath) as! ParentCollectionViewCell
cell.backgroundColor = .red
return cell
}
}
ParentCollectionViewCell
class ParentCollectionViewCell: UICollectionViewCell {
var childCollectionView: UICollectionView = {
let _collectionView = UICollectionView.init(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout.init())
return _collectionView
}()
let reuseId = "ChildCollectionViewCell"
private let data = ["ChildCell1","ChildCell2","ChildCell3"]
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
setupViews()
}
func setupViews() {
contentView.addSubview(childCollectionView)
childCollectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
childCollectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
childCollectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
childCollectionView.topAnchor.constraint(equalTo: contentView.topAnchor),
childCollectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
childCollectionView.widthAnchor.constraint(equalToConstant: 360)
])
childCollectionView.dataSource = self
childCollectionView.register(UINib(nibName: reuseId, bundle: nil), forCellWithReuseIdentifier: reuseId)
if let flowLayout = childCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
}
}
extension ParentCollectionViewCell: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseId, for: indexPath) as! ChildCollectionViewCell
cell.backgroundColor = .gray
cell.setupViews()
return cell
}
}
ChildCollectionViewCell
class ChildCollectionViewCell: UICollectionViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func setupViews() {
let label = UILabel()
label.text = "Child Collection"
label.numberOfLines = 0
label.font = label.font.withSize(50)
contentView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
label.topAnchor.constraint(equalTo: contentView.topAnchor),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
}
Current Output
Current Output
Expected Output
Expected Output

You need to adjust your parent collection height with this line
ChildCollectionView.collectionViewLayout.collectionViewContentSize.height
Please take a look at this it will help you to understand how to handle your parent collection height with childCollection Content
How to adjust height of UICollectionView to be the height of the content size of the UICollectionView?

Related

Swift UICollectionView inside UITableViewCell Programmatically

Greeting folks,
I'm trying to add UICollectionView inside UITableViewCell Programmatically , however the collectionView is not called ever. I went through the code several times with no luck. I looked around to find out also with no luck. I went through Youtube tutorials I found them do the same.
would you help please ☚ī¸
class adsTableViewCell: UITableViewCell {
var adsImages: [UIImage]? {
didSet{
collectionView.reloadData()
}
}
let cellID = "Celled"
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 10
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = UIColor.red
return cv
}()
override func awakeFromNib() {
super.awakeFromNib()
self.collectionView.delegate = self
self.collectionView.dataSource = self
collectionView.register(imageCell.self, forCellWithReuseIdentifier: cellID)
collectionView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
collectionView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
collectionView.showsHorizontalScrollIndicator = false
collectionView.isPagingEnabled = false
self.addSubview(collectionView)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension adsTableViewCell: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! imageCell
if let imageName = adsImages?[indexPath.item]{
cell.imageView.image = imageName
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 300, height: frame.height - 10)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 14, bottom: 0, right: 14)
}
private class imageCell: UICollectionViewCell {
let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.layer.cornerRadius = 10
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup(){
addSubview(imageView)
imageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}
This is the UITableView, cellForRowAt
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "addViewCell", for: indexPath) as! adsTableViewCell
cell.adsImages = images
cell.collectionView.reloadData()
cell.backgroundColor = UIColor.systemTeal
cell.selectionStyle = .none
return cell
}
I found the Solution
simply I had to change :
override func awakeFromNib() {
To
override required init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
and it is work fine now

Header Not Showing in UICollectionView

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:

SwipeCellKit does not swipe cell

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:

collectionView didSelectItemAt superview not selecting the correct Object

I have a list of sensors in a collectionView inside a UIView class called MyCropView where I show some data about that crop.
Im trying to change the value of some sensors like the icon and the background color on each click.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let myCropCard = collectionView.superview as! MyCropCard
let sensor = myCropCard.sensors[indexPath.row]
let cell = myCropCard.superview
//Change the color and icon of the sensors
if let cell = collectionView.cellForItem(at: indexPath) as? MyCollectionViewCell {
//Previous cell in grey
if !self.lastSelectedIndexPath.isEmpty {
let lastCell = collectionView.cellForItem(at: self.lastSelectedIndexPath) as? MyCollectionViewCell
lastCell?.imageView.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0)
lastCell?.imageView.image = UIImage(named: self.lastSelectedImageName )
}
//Makes sensor green onSelect
cell.imageView.backgroundColor = UIColor(red: 0.882, green: 0.95, blue: 0.882, alpha: 1.0)
cell.imageView.image = UIImage(named: sensor.getType() )
self.lastSelectedIndexPath = indexPath
self.lastSelectedImageName = String(format: "%#_deactivated", sensor.getType())
}
//Show all the alerts of each sensor
for alert:Alert in sensor.getAlerts() {
let viewSensor = cell?.viewWithTag(300) as! SensorView
viewSensor.lblSensor.text = sensor.getTipo()
viewSensor.lblBotLog.text = alerta.getNombreDispositivo()
viewSensor.lblMin.text = String(format: "MIN %.0f", alert.getMin())
viewSensor.lblMax.text = String(format: "MAX %.0f", alert.getMax())
viewSensor.btnDel.tag = Int(alert.getId() + 1000)
viewSensor.btnDel.addTarget(
self,
action: #selector( viewSensor.deleteAlert(_:) ),
for: .touchUpInside
)
viewSensor.isHidden = false
}
}
That collectionView is the list of sensors we have. It works fine with 1st MyCropCard, But doesn't for multiple MyCropCards. Superview takes the first cropCard of them all, not the collectionView's parent.
I would like to know how can I correctly have the parent of the selected collectionView?
Your code is cluttered with unneccessary logic-related codes, which are probably unrelated to the issue.
Also, extensive use of tags are making the code unscalable and hard to maintain, so it probably needs a heavy refactoring.
The following code is basically a MWE for what you want to achieve (If I misunderstood your issue, let me know).
To explain the code, this is a custom tableViewCell:
class TableViewCell: UITableViewCell {
var collectionView: UICollectionView? {
didSet {
guard let collectionView = collectionView else { return }
contentView.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .white
setupCollectionViewLayout(collectionView)
}
}
private func setupCollectionViewLayout(_ collectionView: UICollectionView) {
NSLayoutConstraint.activate([
collectionView.leadingAnchor
.constraint(equalTo: contentView.leadingAnchor),
collectionView.trailingAnchor
.constraint(equalTo: contentView.trailingAnchor),
collectionView.topAnchor
.constraint(equalTo: contentView.topAnchor),
collectionView.bottomAnchor
.constraint(equalTo: contentView.bottomAnchor),
])
}
}
setupCollectionViewLayout simply enables AutoLayout. collectionView will be later added when a tableViewCell instance is dequeued.
class CollectionViewCell: UICollectionViewCell {}
class ViewController: UIViewController {
private var lastSelectedCollectionViewCell: CollectionViewCell?
private lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
tableView.register(TableViewCell.self, forCellReuseIdentifier: "cell")
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(tableView)
setupTableViewLayout(tableView)
}
private func setupTableViewLayout(_ tableView: UITableView) {
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
}
tableView is added as the view is loaded, and AutoLayout constraints are added by setupTableViewLayout.
Note that you keep a copy of the last index path, but I think it is better and simpler to keep a reference to the cell itself.
In UITableViewDataSource extension:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: "cell",
for: indexPath) as! TableViewCell
guard cell.collectionView == nil else { return cell }
let collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: UICollectionViewFlowLayout())
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "cell")
cell.collectionView = collectionView
return cell
}
}
extension ViewController: UITableViewDelegate {}
I add a collectionView only if it doesn't already exist in a tableViewCell.
collectionView assigns dataSource and delegate as self, though it would be better if there is an extra modelController object.
UITableViewDelegate is empty.
Now extensions for the collectionViews:
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .gray
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
lastSelectedCollectionViewCell?.backgroundColor = .gray
if let cell = collectionView.cellForItem(at: indexPath) as? CollectionViewCell {
cell.backgroundColor = .yellow
lastSelectedCollectionViewCell = cell
}
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 20, height: 20)
}
}
The important part is collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath), and the others are just boilerplate code.
When a collectionViewCell is selected, the self.lastSelectedCollectionViewCell's color turns back to gray, and the newly selected cell is assigned to lastSelectedCollectionViewCell, after its backgroundColor changed.
The following is the entire code you can run in Playground:
import UIKit
import PlaygroundSupport
class TableViewCell: UITableViewCell {
var collectionView: UICollectionView? {
didSet {
guard let collectionView = collectionView else { return }
contentView.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .white
setupCollectionViewLayout(collectionView)
}
}
private func setupCollectionViewLayout(_ collectionView: UICollectionView) {
NSLayoutConstraint.activate([
collectionView.leadingAnchor
.constraint(equalTo: contentView.leadingAnchor),
collectionView.trailingAnchor
.constraint(equalTo: contentView.trailingAnchor),
collectionView.topAnchor
.constraint(equalTo: contentView.topAnchor),
collectionView.bottomAnchor
.constraint(equalTo: contentView.bottomAnchor),
])
}
}
class CollectionViewCell: UICollectionViewCell {}
class ViewController: UIViewController {
private var lastSelectedCollectionViewCell: CollectionViewCell?
private lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
tableView.register(TableViewCell.self, forCellReuseIdentifier: "cell")
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(tableView)
setupTableViewLayout(tableView)
}
private func setupTableViewLayout(_ tableView: UITableView) {
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: "cell",
for: indexPath) as! TableViewCell
guard cell.collectionView == nil else { return cell }
let collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: UICollectionViewFlowLayout())
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "cell")
cell.collectionView = collectionView
return cell
}
}
extension ViewController: UITableViewDelegate {}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .gray
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
lastSelectedCollectionViewCell?.backgroundColor = .gray
if let cell = collectionView.cellForItem(at: indexPath) as? CollectionViewCell {
cell.backgroundColor = .yellow
lastSelectedCollectionViewCell = cell
}
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 20, height: 20)
}
}
PlaygroundPage.current.liveView = ViewController()

How to properly add an UICollectionView inside UITableViewCell using IOS 12

For some reasons I cannot properly display some items from a collection inside an tableview cell when using Xcode 10 beta. I tried all I know for the last 4 days.
I made a small project sample to see what my issue is.
Full code is here if someone wants to run it locally: https://github.com/adrianstanciu24/CollectionViewInsideUITableViewCell
I first add an table view with 2 different resizable cells, first cell has the collection view and a label:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "collCell") as! CollectionTableViewCell
cell.collectionView.collectionViewLayout.invalidateLayout()
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "normalCell")!
return cell
}
}
}
Here is the cell with collection view defined. This also has a height constraint which I updated in layoutSubviews:
class CollectionTableViewCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
collectionView.delegate = self
collectionView.dataSource = self
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
}
override func layoutSubviews() {
super.layoutSubviews()
collectionViewHeightConstraint.constant = collectionView.collectionViewLayout.collectionViewContentSize.height
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "taskCell", for: indexPath as IndexPath) as! TaskCollectionViewCell
cell.name.text = "Task_" + String(indexPath.row)
return cell
}
}
Below is a screenshot with the collection view constraint and how the storyboard looks:
And this is how it looks when running:
The cells are squashed and label on top disappears. What I want is the collection view should grow to show all elements and also making table view cell to resize, but that is not happening at the moment and I can't figure out where the issue is.
If you want the following output:
Code
ViewController:
class ViewController: UIViewController {
let list = [String](repeating: "Label", count: 10)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
tableView.reloadData()
}
public lazy var tableView: UITableView = { [unowned self] in
let v = UITableView.init(frame: .zero)
v.delegate = self
v.dataSource = self
v.estimatedRowHeight = 44
v.rowHeight = UITableViewAutomaticDimension
v.register(TableCell.self, forCellReuseIdentifier: "TableCell")
return v
}()
}
extension ViewController: UITableViewDelegate{
}
extension ViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableCell", for: indexPath) as! TableCell
cell.label.text = "\(list[indexPath.row]) \(indexPath.row)"
return cell
}
}
TableCell:
class TableCell: UITableViewCell{
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
func commonInit(){
contentView.addSubview(label)
contentView.addSubview(collectionView)
updateConstraints()
}
override func updateConstraints() {
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: topAnchor),
label.leadingAnchor.constraint(equalTo: leadingAnchor),
label.trailingAnchor.constraint(equalTo: trailingAnchor)
])
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: label.bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor),
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
])
super.updateConstraints()
}
let list = [String](repeating: "Task_", count: 10)
public let label: UILabel = {
let v = UILabel()
v.textAlignment = .center
return v
}()
public lazy var collectionView: UICollectionView = { [unowned self] in
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
let v = UICollectionView(frame: .zero, collectionViewLayout: layout)
v.register(CollectionCell.self, forCellWithReuseIdentifier: "CollectionCell")
v.delegate = self
v.dataSource = self
v.isScrollEnabled = false
return v
}()
override func sizeThatFits(_ size: CGSize) -> CGSize {
let collectionCellHeight = 50
let rows = list.count / 5 // 5: items per row
let labelHeight = 20 // you can calculate String height
let height = CGFloat((collectionCellHeight * rows) + labelHeight)
let width = contentView.frame.size.width
return CGSize(width: width, height: height)
}
}
extension TableCell: UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return list.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionCell
cell.label.text = "\(list[indexPath.item])\(indexPath.item)"
return cell
}
}
extension TableCell: UICollectionViewDelegate{
}
extension TableCell: UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width/5, height: 50)
}
}
CollectionCell
class CollectionCell: UICollectionViewCell{
let padding: CGFloat = 5
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit(){
backgroundColor = .yellow
contentView.addSubview(label)
updateConstraints()
}
override func updateConstraints() {
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: topAnchor, constant: padding),
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding),
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding),
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -padding)
])
super.updateConstraints()
}
public let label: UILabel = {
let v = UILabel()
v.textColor = .darkText
v.minimumScaleFactor = 0.5
v.numberOfLines = 1
return v
}()
}
Made with Swift 5.3 and tested on iOS 14, the follow it should work just copy and paste 😉:
TableViewController.swift
import UIKit
import SnapKit
class TableViewController: UIViewController {
private let tableView: UITableView = UITableView()
private let tableViewCellId = "TableViewCell"
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
}
// MARK: - Setup UIs
extension TableViewController {
private func setupUI() {
tableView.delegate = self
tableView.dataSource = self
tableView.register(TableViewCell.self, forCellReuseIdentifier: tableViewCellId)
view.addSubview(tableView)
tableView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
}
// MARK: - Delegate & DataSource
extension TableViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: tableViewCellId) as! TableViewCell
cell.backgroundColor = .white
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
TableViewCell.swift
import UIKit
import SnapKit
class TableViewCell: UITableViewCell {
private let list = [String](repeating: "Row ", count: 10)
private let collectionViewLayout = UICollectionViewFlowLayout()
lazy private var collectionView: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
override func layoutSubviews() {
setupUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
// MARK: - UI Setup
extension TableViewCell {
private func setupUI() {
// layout.minimumLineSpacing = 5
// layout.minimumInteritemSpacing = 5
collectionViewLayout.scrollDirection = .horizontal
collectionView.backgroundColor = .clear
collectionView.showsHorizontalScrollIndicator = false
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionCell")
collectionView.delegate = self
collectionView.dataSource = self
addSubview(collectionView)
collectionView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
}
extension TableViewCell: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return list.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionViewCell
cell.titleLabel.text = "\(list[indexPath.item])\(indexPath.item)"
return cell
}
}
extension TableViewCell: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width / 5, height: 100)
}
}
CollectionViewCell.swift
import UIKit
class CollectionViewCell: UICollectionViewCell {
var titleLabel: UILabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
setupUI()
}
}
// MARK: - UI Setup
extension CollectionViewCell {
private func setupUI() {
titleLabel.font = UIFont(name: "HelveticaNeue", size: 20)
titleLabel.textColor = .white
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.backgroundColor = .systemGreen
titleLabel.textAlignment = .center
addSubview(titleLabel)
titleLabel.snp.makeConstraints {
$0.centerX.centerY.equalToSuperview()
$0.height.equalTo(60)
$0.width.equalTo(150)
}
}
}
If your desired output is following
Then replace your code of collectionview with this
class CollectionTableViewCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!
var arrForString:[String] = ["Task_0","Task_1","Task_3","Task_4","Task_5","Task_6","Task_7","Task_8","Task_9","Task_10"]
override func awakeFromNib() {
super.awakeFromNib()
collectionView.delegate = self
collectionView.dataSource = self
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrForString.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "taskCell", for: indexPath as IndexPath) as! TaskCollectionViewCell
cell.name.text = arrForString[indexPath.row]
cell.layoutIfNeeded()
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
//we are just measuring height so we add a padding constant to give the label some room to breathe!
var padding: CGFloat = 10.0
var height = 10.0
height = Double(estimateFrameForText(text: arrForString[indexPath.row]).height + padding)
return CGSize(width: 50, height: height)
}
private func estimateFrameForText(text: String) -> CGRect {
//we make the height arbitrarily large so we don't undershoot height in calculation
let height: CGFloat = 120
let size = CGSize(width: 50, height: height)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18, weight: UIFont.Weight.light)]
return NSString(string: text).boundingRect(with: size, options: options, attributes: attributes, context: nil)
}
}
feel free to ask.

Resources