Here is a visual example of what I would like to achieve.
My initial idea was to use a UICollectionView with custom cells, but that didn't work out very well and trying out collectionview within a collectionview was even worse.
So I tried to do a collectionview in a tableview and that kind of worked but the tableviewcell's dynamic height proved to be a gigantic issue which was super difficult for me to solve as I have only just begun to learn swift and iOS developing. Please help :(
EDIT:
note that the "Shopping Mall 1" section is meant to be a sticky header of sort. Similar to this (the turquoise header):
credits: https://github.com/jamztang/CSStickyHeaderFlowLayout
but I was using https://github.com/petec-blog/CollectionViewStickyHeaders as an example as it was clearer and closer to what I needed.
You could play around with the code below to get the idea (Xcode 7.2 (7C68))
Delete storyboard, launchscreen, Clean properties Main story board and Launchscreen in Info.plist and replace AppDelegate.swift with the following content
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window!.rootViewController = CollectionViewInTableView()
self.window!.makeKeyAndVisible()
return true
}
}
class CollectionViewInTableView: UIViewController, UITableViewDelegate, UITableViewDataSource {
let table = UITableView()
override func viewDidLoad() {
table.delegate = self
table.dataSource = self
self.view.backgroundColor = UIColor.whiteColor()
table.registerClass(Cell.self, forCellReuseIdentifier: Cell.self.description())
self.view.addSubviewWithConstraints(["v" : table], constraints: ["H:|[v]|", "V:|-50-[v]|"])
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return 2}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {return 2}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {return "Shopping Moll \(section)"}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return tableView.dequeueReusableCellWithIdentifier(Cell.self.description(), forIndexPath: indexPath) as! Cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {return 200}
}
class Cell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var collectionView: UICollectionView!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = UITableViewCellSelectionStyle.None
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: 75, height: 60)
layout.headerReferenceSize = CGSize(width: contentView.frame.width, height: 20)
collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: UIScreen.mainScreen().bounds.width, height: 200), collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.registerClass(Item.self, forCellWithReuseIdentifier: Item.self.description())
collectionView.registerClass(SectionTitle.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: SectionTitle.self.description())
self.backgroundColor = UIColor.yellowColor()
collectionView.backgroundColor = UIColor.whiteColor()
self.contentView.addSubview(collectionView)
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {return 10}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCellWithReuseIdentifier(Item.self.description(), forIndexPath: indexPath) as! Item
}
required init?(coder: NSCoder) {super.init(coder: coder)}
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: SectionTitle.self.description(), forIndexPath: indexPath) as! SectionTitle
headerView.label.text = "Shop \(indexPath.section)"
return headerView
default: assert(false, "Unexpected element kind")
}
}
}
class SectionTitle: UICollectionReusableView {
var label: UILabel!
override init(frame: CGRect) {
super.init(frame:CGRectZero)
label = UILabel()
label.text = "text"
self.addSubviewWithConstraints(["v" : label], constraints: ["H:|-10-[v]|","V:|[v]|"])
}
required init?(coder aDecoder: NSCoder) {fatalError("init(coder:) has not been implemented")}
}
class Item: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
let v = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
v.backgroundColor = UIColor.blueColor()
contentView.addSubview(v)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension UIView {
func addSubviewWithConstraints(views: [String : AnyObject], constraints: Array<String>) -> [String : AnyObject] {
for (_, view) in views {
self.addSubview(view as! UIView)
(view as! UIView).translatesAutoresizingMaskIntoConstraints = false
}
for var i = 0; i < constraints.count; i++ {self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(constraints[i], options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))}
return views
}
}
Related
I noticed that Topics CollectionViewCell was not gone when I debugged. Why is that?
the cells are all correct but the collectionview is not available.
The link of the project is below. you can also look from there.
here I define cell.
extension ArticlesVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CollectionTableViewCell.identifier, for: indexPath) as! CollectionTableViewCell
cell.configure(with: models)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}
}
struct Model {
let text: String
let imageName: String
init(text: String, imageName: String) {
self.text = text
self.imageName = imageName
}
}
Here I define CollectionTableViewCell
class CollectionTableViewCell: UITableViewCell {
static let identifier = "CollectionTableViewCell"
var collectionView: UICollectionView?
var models = [Model]()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
collectionView?.register(TopicsCollectionViewCell.self, forCellWithReuseIdentifier: TopicsCollectionViewCell.identifier)
collectionView?.delegate = self
collectionView?.dataSource = self
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
contentView.addSubview(collectionView!)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
collectionView?.frame = contentView.bounds
}
func configure(with models: [Model]) {
self.models = models
collectionView?.reloadData()
}
}
extension CollectionTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return models.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TopicsCollectionViewCell.identifier, for: indexPath) as! TopicsCollectionViewCell
cell.configure(with: models[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 250, height: 250)
}
}
**This TopicsCollectionViewCell **
class TopicsCollectionViewCell: UICollectionViewCell {
static let identifier = "TopicsCollectionViewCell"
let titleLabel: UILabel = {
let label = UILabel()
label.text = "label"
return label
}()
let photoImage: UIImageView = {
let imageView = UIImageView()
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(titleLabel)
contentView.addSubview(photoImage)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
photoImage.frame = CGRect(x: 3,
y: 3,
width: contentView.width - 6,
height: 150)
titleLabel.frame = CGRect(x: 3,
y: photoImage.bottom + 5,
width: contentView.width - 6,
height: contentView.height - photoImage.height - 6)
}
public func configure(with model: Model) {
print(model)
self.titleLabel.text = model.text
self.photoImage.image = UIImage(named: model.imageName)
}
}
This link is project with github
During execution those lines are useless as collectionView? is nil
collectionView?.register(TopicsCollectionViewCell.self, forCellWithReuseIdentifier: TopicsCollectionViewCell.identifier)
collectionView?.delegate = self
collectionView?.dataSource = self
You need to reorder the code by initing the collectionView first
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
contentView.addSubview(collectionView!)
collectionView?.register(TopicsCollectionViewCell.self, forCellWithReuseIdentifier: TopicsCollectionViewCell.identifier)
collectionView?.delegate = self
collectionView?.dataSource = self
I have the Main View Controller which has a collection view with its collection view cells each initialized as a tableView to serve multiple rows inside of that collection view cell. If you're getting confused, below is the snapshot of the current state.
The problem is when I try to tap a tableView row cell to open another view controller, It fails and a selected state of table view cell is shown.
Here is the snapshot.
//HomeCollectionViewCell.swift
class HomeCollectionViewCell: UICollectionViewCell {
override func layoutSubviews() {
super.layoutSubviews()
setUpCellView()
}
func setUpCellView() {
let frame = CGRect(x:20, y:20, width: bounds.width - 40, height: 600)
let cell = CellView(frame: frame)
contentView.addSubview(cell)
}
}
//CellView.swift
class CellView: UITableView {
let quoteCell = "QuoteCell"
let newsCell = "NewsCell"
let articleCell = "ArticleCell"
override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: .grouped)
self.layer.cornerRadius = 15
self.backgroundColor = .white
self.dataSource = self
self.delegate = self
self.register(QuoteTableViewCell.self, forCellReuseIdentifier: quoteCell)
self.register(NewsTableViewCell.self, forCellReuseIdentifier: newsCell)
self.register(ArticleTableViewCell.self, forCellReuseIdentifier: articleCell)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension CellView: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch indexPath.section {
case 0: return 35
case 1: return 140
case 2: return 100
case 3: return 140
default: return 0
}
}
}
extension CellView: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return categories.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return categories[section]
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section {
case 0: let cell = tableView.dequeueReusableCell(withIdentifier: dateCell)
cell?.textLabel?.text = "Today"
cell?.textLabel?.font = UIFont.systemFont(ofSize: 30, weight: UIFont.Weight.heavy)
return cell!
case 1: let cell = tableView.dequeueReusableCell(withIdentifier: quoteCell) as! QuoteTableViewCell
return cell
case 2: let cell = tableView.dequeueReusableCell(withIdentifier: newsCell) as! NewsTableViewCell
return cell
case 3: let cell = tableView.dequeueReusableCell(withIdentifier: articleCell) as! ArticleTableViewCell
return cell
default: let cell = tableView.dequeueReusableCell(withIdentifier: commonCell)
cell?.textLabel?.text = "LOL"
return cell!
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch indexPath.section {
case 0: print("Date Selected")
case 1: print("Quote Selected")
case 2: print("News Selected")
case 3: let homeViewController = HomeViewController()
let articleDetailViewController = ArticleDetailViewController()
//homeViewController.show(articleDetailViewController, sender: homeViewController)//homeViewController.navigationController?.pushViewController(articleDetailViewController, animated: true)
homeViewController.present(articleDetailViewController, animated: true, completion: nil)
print("Article selected")
default: print("LOL")
}
}
}
//HomeViewController.swift
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupNavBar()
view.addSubview(collectionView)
setUpConstraints()
configure(collectionView: collectionView)
}
func setUpConstraints() {
_ = collectionView.anchor(view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 10, leftConstant: 10, bottomConstant: 10, rightConstant: 10, widthConstant: 0, heightConstant: 0)
collectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
lazy var collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
let cv = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.alwaysBounceVertical = true
cv.clipsToBounds = true
cv.showsHorizontalScrollIndicator = false
cv.showsVerticalScrollIndicator = false
cv.backgroundColor = .clear
cv.isHidden = false
return cv
}()
}
private let reuseIdentifier = "Cell"
extension HomeViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
internal func configure(collectionView: UICollectionView) {
collectionView.register(HomeCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 20, right: 0)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! HomeCollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.width, height: 600)
}
}
Please tell where I'm doing wrong or What approach should I use?
Note- No use of storyboards/IB. Done things programmatically only.
Give identifiers("HomeViewController" and "ArticleDetailViewController") to view controllers and try below code in didSelectRow().
case 3:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let sourceViewController = storyboard.instantiateViewController(withIdentifier: "HomeViewController") as? HomeViewController
let destinationViewController = storyboard.instantiateViewController(withIdentifier: "ArticleDetailViewController") as? ArticleDetailViewController
let navigator: UINavigationController = sourceViewController as! UINavigationController
navigator.pushViewController(destinationViewController!, animated: true)
From what have you done, I want to point out that its not a good idea to present UIViewController from UView. You must write some custom delegates which will get fired once someone taps on those cells in the custom CellView class. Those delegates must be implemented in the view controller that contains the tableview. From the UIViewController you must write the code to present the new viewcontrollers.
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.
I have created UITableView with multiple sections. Each section has one line (UITableViewCell) and inside this line, I have UICollectionView and inside it UICollectionViewCell. Scrolling is working correctly. However, when I change the table row height, UICollectionView height remain unchaged. How to fix this?
class ViewController: UIViewController {
var tab: HorizontalSelector? = nil
#IBOutlet weak var tabView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
tab = HorizontalSelector(frame: self.tabView.frame)
tab?.addSection("Jedna", withHeight: 100)
tab?.addSection("Middle", withHeight: 100)
tab?.addSection("Dva", withHeight: 100)
self.tabView.addSubview(tab!)
}
}
Class for horizontal selector
class HorizontalSelector: UITableView, UITableViewDataSource, UITableViewDelegate {
let CELL_REUSE_ID = "cellSectionRow"
let DEFAULT_ROW_HEIGHT: CGFloat = 44
var sectionHeights: [CGFloat] = []
var sections: [String] = []
init(frame: CGRect){
var f: CGRect = frame
f.origin.x = 0
f.origin.y = 0
super.init(frame: f, style: UITableViewStyle.plain)
self.initialize()
}
override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func initialize(){
self.register(SectionCell.self, forCellReuseIdentifier: CELL_REUSE_ID)
self.dataSource = self
self.delegate = self
self.backgroundColor = UIColor.blue
}
func addSection(_ section: String){
sections.append(section)
sectionHeights.append(DEFAULT_ROW_HEIGHT)
}
func addSection(_ section: String, withHeight: CGFloat){
sections.append(section)
sectionHeights.append(withHeight)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return sectionHeights[indexPath.section]
}
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section]
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sectionCell = tableView.dequeueReusableCell(withIdentifier: CELL_REUSE_ID) as! SectionCell
sectionCell.backgroundColor = UIColor.brown
return sectionCell
}
}
Class for single table section
class SectionCell: UITableViewCell {
required init?(coder aDecoder: NSCoder){
fatalError("init(coder:) has not been implemented")
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.initialize()
}
override func awakeFromNib() {
super.awakeFromNib()
self.initialize()
}
func initialize(){
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
self.contentView.addSubview(SectionCellRowCollection(frame: self.contentView.bounds, collectionViewLayout: layout))
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Class for collection within single table section
class SectionCellRowCollection: UICollectionView, UICollectionViewDataSource, UICollectionViewDelegate {
private let COLLECTION_CELL_ID = "collectionCell"
required init?(coder aDecoder: NSCoder){
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout){
super.init(frame: frame, collectionViewLayout: layout)
self.initialize()
}
private func initialize(){
self.register(SectionCellRowCollectionCell.self, forCellWithReuseIdentifier: COLLECTION_CELL_ID)
self.dataSource = self
self.delegate = self
self.backgroundColor = UIColor.yellow
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: COLLECTION_CELL_ID, for: indexPath as IndexPath) as! SectionCellRowCollectionCell
cell.backgroundColor = UIColor.gray
return cell
}
}
Class for single cell inside colection
class SectionCellRowCollectionCell: UICollectionViewCell{
var imageView: UIImageView!
required init?(coder aDecoder: NSCoder){
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height*2/3))
imageView.contentMode = UIViewContentMode.scaleAspectFit
imageView.image = UIImage(named: "Swift-cover")
contentView.addSubview(imageView)
self.backgroundColor = UIColor.green
}
}
Setup constraints for your collectionView and it will change its frame base on these constraints:
class SectionCell: UITableViewCell {
private var collectionView: SectionCellRowCollection!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
initialize()
}
override func awakeFromNib() {
super.awakeFromNib()
initialize()
}
private func initialize() {
translatesAutoresizingMaskIntoConstraints = false
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
collectionView = SectionCellRowCollection(frame: contentView.bounds, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(collectionView)
// constraints
collectionView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
collectionView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
collectionView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
}
}
You have collectionView inside the tableviewCell so there is some static height of collectionView in storyboard so whatever the height of tableview the collectionView takes its height as its not clip to bounds.So to resolve that problem you must give height of tableView
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return 100.0;//Choose your custom row height
}
Then set the collectionView cell height that must be cell or equal to tableview height
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSize(width: screenWidth, height:90 );
}
I have seen many examples of a uicollectionview in a tableview cell with older xcode versions, but am having trouble finding/figuring out how to develop using swift. I have been playing with this source code... http://www.brianjcoleman.com/tutorial-collection-view-using-swift/ , but am wondering how to introduce or build UICollectionViews in tableViewCells using Swift...?
Thank you in advance for any direction or resources on how to do this Using Swift and Storyboards?
// ViewController.swift
// UICollectionView
import UIKit
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
#IBOutlet var collectionView: UICollectionView?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: 90, height: 90)
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView!.dataSource = self
collectionView!.delegate = self
collectionView!.registerClass(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
collectionView!.backgroundColor = UIColor.whiteColor()
self.view.addSubview(collectionView!)
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 20
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as CollectionViewCell
cell.backgroundColor = UIColor.blackColor()
cell.textLabel?.text = "\(indexPath.section):\(indexPath.row)"
cell.imageView?.image = UIImage(named: "circle")
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// CollectionViewCell.swift
// UICollectionView
import UIKit
class CollectionViewCell: UICollectionViewCell {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
let textLabel: UILabel!
let imageView: UIImageView!
override init(frame: CGRect) {
super.init(frame: frame)
imageView = UIImageView(frame: CGRect(x: 0, y: 16, width: frame.size.width, height: frame.size.height*2/3))
imageView.contentMode = UIViewContentMode.ScaleAspectFit
contentView.addSubview(imageView)
let textFrame = CGRect(x: 0, y: 32, width: frame.size.width, height: frame.size.height/3)
textLabel = UILabel(frame: textFrame)
textLabel.font = UIFont.systemFontOfSize(UIFont.smallSystemFontSize())
textLabel.textAlignment = .Center
contentView.addSubview(textLabel)
}
}
Here you are ;)
class ViewController: UITableViewController, UICollectionViewDataSource, UICollectionViewDelegate {
let data: Array<Int>= [1,2,3,4,5,6,7,8,9,10]
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell
return cell
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 20
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as UICollectionViewCell
return cell
}
}
This is a Swift 2.0 example of collectionView inside a collectionViewCell, but the same applies to tableView's as well.
https://github.com/irfanlone/Collection-View-in-a-collection-view-cell
Using a collection view has certainly some advantages over tableviews in this case. Other useful Links:
Tutorial : https://ashfurrow.com/blog/putting-a-uicollectionview-in-a-uitableviewcell-in-swift/
Source Code: https://github.com/ashfurrow/Collection-View-in-a-Table-View-Cell
Hope that helps!!