Adding a collection view inside a table view cell swift 3 - ios

I am trying to display table view and once you select a row it shall expand to show a collection view grid of the products within that category. I've managed to get the table view cell expanding and displaying an image, by modifying the height constraint of the image (not the xib method, i tried, no success), following the tutorial below however I cannot seem to get it working if i insert a collection view.
http://codepany.com/blog/swift-3-expandable-table-view-cells/
This is what I am trying to achieve:
My code:
Custom Table view Cell Class
class ExpandableCell: UITableViewCell {
#IBOutlet weak var view: UIView!
#IBOutlet weak var label: UILabel!
#IBOutlet weak var img: UIImageView!
#IBOutlet weak var imgHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var viewHeight: NSLayoutConstraint!
var isExpanded:Bool = false
{
didSet
{
if !isExpanded {
self.imgHeightConstraint.constant = 0.0
} else {
self.imgHeightConstraint.constant = 128.0
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
View controller
class NewMenuVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
// #IBOutlet weak var tableViewCell: UITableViewCell!
// #IBOutlet weak var collectionView: UICollectionView!
// #IBOutlet weak var collectionViewCell: UICollectionViewCell!
let imgs = ["0", "1", "2", "0", "1", "2", "0", "1", "2", "0", "1", "2", "0", "1", "2", "0", "1", "2", "0", "1", "2"]
let categories = ["Hamburgers", "Saver Menu", "Sides", "Desserts", "Drinks", "Offers", "Specials", "Wraps", "Chicken", "Meals"]
var expandedRows = Set<Int>()
//#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:ExpandableCell = tableView.dequeueReusableCell(withIdentifier: "ExpandableCell") as! ExpandableCell
cell.img.image = UIImage(named: imgs[indexPath.row])
cell.isExpanded = self.expandedRows.contains(indexPath.row)
cell.label.text = categories[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 144.0
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
else { return }
switch cell.isExpanded
{
case true:
self.expandedRows.remove(indexPath.row)
case false:
self.expandedRows.insert(indexPath.row)
}
cell.isExpanded = !cell.isExpanded
self.tableView.beginUpdates()
self.tableView.endUpdates()
}

Existing Approach:
I would suggest try using a section header(UITableViewHeaderFooterView). Once you tap on the section header you can set the number of rows to 1.
Here you have to make sure the rowHeight = height of number of items in collection.
This would include too much of work.
Simplify the Approach.
Use the collection view directly , with section header and layout configured for veritical Scroll.

CollectionViewCell.swift
import UIKit
class FriendCVCell: UICollectionViewCell {
#IBOutlet weak var FriendNameLabel: UILabel!
#IBOutlet weak var FriendMobileNOLabel: UILabel!
}
tableCell.swift
import UIKit
class MyPlanTVCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var PlanNameLabel: UILabel!
#IBOutlet weak var MallNameLabel: UILabel!
#IBOutlet weak var PlanDateLabel: UILabel!
var FriendNameArray: NSArray = []
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
collectionView.delegate = self as! UICollectionViewDelegate
collectionView.dataSource = self as! UICollectionViewDataSource
FriendNameArray = ["Sunny", "Mike", "Tom", "Henry", "Jenny", "Neena", "Julia" ]
}
}
extension MyPlanTVCell
: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return FriendNameArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! FriendCVCell
//What is this CustomCollectionCell? Create a CustomCell with SubClass of UICollectionViewCell
//Load images w.r.t IndexPath
cell.collectionImageView.image = UIImage(named:"default"[indexPath.row])
cell.FriendNameLabel.text = FriendNameArray[indexPath.row] as! String
cell.backgroundColor = UIColor.green
//gameNames[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let clickedIndex =
imageNames[indexPath.row]
print(clickedIndex)
}
}

Related

CollectionView inside Tableview loading incorrect data for reused cells

My purpose is to show the header string in each label of collectionview cell.But when I run the top cells which have already loaded are showing correct values in labels while after scrolling the reused cells are showing incorrect values.
Note : The data is fetched from a local file.
My data is as follows:
Trending : 20 items
Mustwatch : 3 items
Newly Added : 8 Items
Popular : 2 items
My code:
ViewController.swift
import UIKit
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
var videosList:[VideosJSONElement]?
var videosListInstance:VideosDataAccessModel?
#IBOutlet weak var categoriesTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
videosListInstance = VideosDataAccessModel()
videosList = videosListInstance?.videosList
categoriesTableView.dataSource = self
categoriesTableView.delegate = self
categoriesTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videosList!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "categoriesCell") as! CategoriesVideoCell
cell.videos = videosList![indexPath.row].nodes
cell.position = indexPath.row
cell.categoryTitle.text = videosList![indexPath.row].title
cell.videosCollectionView.reloadData()
cell.sceneContext = self
return cell
}
}
CategoriesVideoCell.swift
import UIKit
class CategoriesVideoCell: UITableViewCell,UICollectionViewDelegate,UICollectionViewDataSource {
#IBOutlet weak var categoryTitle: UILabel!
var videos:[Node]?
var position:Int!
var sceneContext:UIViewController?
#IBOutlet weak var videosCollectionView: UICollectionView!
override func didMoveToSuperview() {
super.didMoveToSuperview()
videosCollectionView.dataSource = self
videosCollectionView.delegate = self
}
override class func awakeFromNib() {
super.awakeFromNib()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return videos!.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
var cell = collectionView.dequeueReusableCell(withReuseIdentifier: "videoCell", for:indexPath) as! VideoCVCell
cell.node = "\(categoryTitle.text!)"
return cell
}}
The first two cells have correct label text
The reused cells have incorrect data in the label

swift - Dynamic List of items inside tableViewCell

What is the best practice to add dynamic list of items in UITableViewCell with showMore/showLess?
UITableView inside UITableViewCell
I used table view inside my UITableViewCell , but I have issue with reload row at indexPath causes jumpy scrolling.
TableViewController Code :
class ViewController: UIViewController , UITableViewDelegate , UITableViewDataSource , delegatCell {
#IBOutlet weak var tableView: UITableView!
var elementsArray: [data] = []
override func viewDidLoad() {
super.viewDidLoad()
for i in 1...20 {
elementsArray.append(data.init(title: "title\(i)", date: "8:00 PM", isShowMoreClicked: false, elements: ["ToDo\(i)","Aqraa\(i)","ToDo\(i)","ToDo\(i)" , "Mobile Team\(i)" , "Testing Data\(i)"]))
// Do any additional setup after loading the view, typically from a nib.
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return elementsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let obj = elementsArray[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! expandedTableViewCell
cell.initializeCell()
print(obj.elements)
cell.data = obj.elements
cell.delegate = self
cell.dateLabel.text = obj.date
cell.titleLabel.text = obj.title
cell.showMore.titleLabel?.font = reqularDynamicFont()
cell.showMore.titleLabel?.adjustsFontSizeToFitWidth = true
cell.showMore.tag = indexPath.row
cell.isShowMoreClicked = obj.isShowMoreClicked
cell.tableView.reloadData()
self.view.layoutIfNeeded()
return cell
}
func reqularDynamicFont()-> UIFont{
let font = UIFont.systemFont(ofSize: 15, weight: .regular)
let fontMetrics = UIFontMetrics(forTextStyle: .caption1)
return fontMetrics.scaledFont(for: font)
}
func reloadTableView(indexpath: IndexPath , isShowMoreClicked: Bool) {
elementsArray[indexpath.row].isShowMoreClicked = isShowMoreClicked
tableView.reloadRows(at: [indexpath], with: .automatic)
}
}
ExpandedTableViewCell with inner table view Code :
import UIKit
protocol delegatCell {
func reloadTableView(indexpath: IndexPath , isShowMoreClicked: Bool)
}
class expandedTableViewCell: UITableViewCell , UITableViewDelegate , UITableViewDataSource{
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var showMore: UIButton!
var data : [String] = []
var initalNum = 2
var isShowMoreClicked: Bool = false
var delegate:delegatCell?
func initializeCell(){
data.removeAll()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isShowMoreClicked{
return data.count
}else{
return initalNum
}
}
#IBAction func reloadData(_ sender: UIButton) {
delegate?.reloadTableView(indexpath: IndexPath(row: sender.tag, section: 0), isShowMoreClicked: !isShowMoreClicked)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print(data)
let cell = tableView.dequeueReusableCell(withIdentifier: "simpleTableViewCell", for: indexPath) as! simpleTableViewCell
cell.descLabel.text = data[indexPath.row]
return cell
}
}
I used auto layout to support dynamic height.

TableView is not displayed how it's supposed to

I created a table view in Xcode and when I run the project it's displayed awfully (pic related).
Don't know what could be the problem.
What I did was:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let fruit = ["Apple", "Prune", "Grapes", "Watermelon", "Melon", "Cherry"]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruit.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell") as! FruitTableViewCell
cell.fruitLable.text = fruit[indexPath.row]
cell.fruitImage.image = UIImage(named: fruit[indexPath.row])
return cell
}
}
import UIKit
class FruitTableViewCell: UITableViewCell {
#IBOutlet weak var fruitView: UIView!
#IBOutlet weak var fruitImage: UIImageView!
#IBOutlet weak var fruitLable: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
You need to implement heightForRowAt
func tableView(_ tableView: UITableView,heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}

UITableView in UICollectionView

I placed a tableView inside a collectionView and added the delegates. The problem is, that I want to control the data in the different tableViews in the collectionView.
Every collectionView holds a label with the name of the current day (e.g.: "Monday"). Below the label is a tableView. In the tableView should be displayed different data, based on the index of the collectionView.
In my example, every tableView has two cells. The tableView in the first item of the collectionView should display: cell1: "1" and cell2: "2", the tableView in the second item should display: cell1: "3" and cell2: "4".
I'm currently using the code below:
import UIKit
class StundenplanCollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
myHours.append(myH0)
myHours.append(myH1)
myHours.append(myH2)
myHours.append(myH3)
myHours.append(myH4)
myHours.append(myH5)
collectionView.delegate = self
collectionView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: Outlets
#IBOutlet weak var collectionView: UICollectionView!
//MARK: Variables
var myDays = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag"]
var myHours: [[String]] = Array() //Array that holds myH1 - myH5
var myH0 = ["1", "2"]
var myH1 = ["3", "4"]
var myH2 = ["5", "6"]
var myH3 = ["7", "8"]
var myH4 = ["9", "10"]
var myH5 = ["Error", "Error"]
var tableLoop = 0
var headerColor: UIColor = UIColor( red: CGFloat(255/255.0), green: CGFloat(140/255.0), blue: CGFloat(60/255.0), alpha: CGFloat(1.0))
//MARK: Table and Collection View
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return myDays.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: StundenplanCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "stundenplanCollectionCell", for: indexPath) as! StundenplanCollectionViewCell
cell.titleLabel.text = myDays[indexPath.row]
cell.titleLabel.backgroundColor = headerColor
cell.titleLabel.textColor = UIColor.white
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myHours[tableLoop].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: StundenplanTableViewCell = tableView.dequeueReusableCell(withIdentifier: "stundenplanTableCell", for: indexPath) as! StundenplanTableViewCell
cell.titleLabel.text = myHours[/*Here should the indexPath.row of the collectionView item be placed*/][indexPath.row]
return cell
}
}
class StundenplanCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
}
class StundenplanTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
}
Image 1: Here you can see the first tableView in the collectionView (first item of collectionView):
Image 2: Here you can see the second tableView in the collectionView (second Item of collectionView):
Image 3: Storyboard:
Image 4: Structure of the different objects in the view:
Updated code (according to the answer by #missionMan - Thanks by the way!):
import UIKit
class StundenplanCollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = false
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
myHours.append(myH0)
myHours.append(myH1)
myHours.append(myH2)
myHours.append(myH3)
myHours.append(myH4)
myHours.append(myH5)
collectionView.delegate = self
collectionView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: Outlets
#IBOutlet weak var collectionView: UICollectionView!
//MARK: Variables
var myDays = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag"]
var myHours: [[String]] = Array() //Array that holds myH1 - myH5
var myH0 = ["1", "2"]
var myH1 = ["3", "4"]
var myH2 = ["5", "6"]
var myH3 = ["7", "8"]
var myH4 = ["9", "10"]
var myH5 = ["Error", "Error"]
var headerColor: UIColor = UIColor( red: CGFloat(255/255.0), green: CGFloat(140/255.0), blue: CGFloat(60/255.0), alpha: CGFloat(1.0))
//MARK: Table and Collection View
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return myDays.count
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "stundenplanCollectionViewCell") as? StundenplanCollectionViewCell else { //how can I access the inner tableView? Error: Use of unresolved identifier 'tableView'
return UICollectionViewCell()
}
//set data and reload inner table from outer view controller (StundenplanCollectionViewController according to your code)
cell.dayItems = myHours[indexPath.row]
cell.tableView.reloadData()
//...
return cell
}
class StundenplanCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
var dayItems: [String] = []
override func awakeFromNib() {
super.awakeFromNib()
self.tableView.delegate = self // <-- set delegates inner table
self.tableView.dataSource = self
}
}
extension StundenplanCollectionViewCell: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dayItems.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: StundenplanTableViewCell = (tableView.dequeueReusableCell(withIdentifier: "stundenplanTableViewCell") as? StundenplanTableViewCell)!
//...
return cell
}
}
class StundenplanTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
}
You can add table delegate methods in table cell class
and then you can set data for one day. So each collection cell will have its own table and its own data.
like this:
class StundenplanCollectionViewCell: UICollectionViewCell { //<-- outer cell
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var innerTableView: UITableView! //<-- declare inner table
var dayItems: [String] = []
override func awakeFromNib() {
super.awakeFromNib()
self.innerTableView.delegate = self // <-- set delegates inner table
self.innerTableView.dataSource = self
}
}
extension StundenplanCollectionViewCell: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dayItems.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
...
}
...
set data from outer cell of your collectionView in StundenplanCollectionViewController
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withIdentifier: "stundenplanCollectionViewCell") as? StundenplanCollectionViewCell else { //<-- HERE
return UICollectionViewCell()
}
//set data and reload inner table from outer view controller (StundenplanCollectionViewController according to your code)
cell.dayItems = myH[indexPath.row]
cell.innerTableView.reloadData()
...
return cell
}
Why are you trying to dequeue a UICollectionViewCell from a UITableView?
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
...
}

Swift 3: Custom TableView Cell not showing

hey so I'm trying to create a custom cell and I've properly linked everything in my story board but for some reason it doesn't display anything when I run. I Tried testing it out, it appears that the custom cell objects don't even get created.
class customCell : UITableViewCell{
#IBOutlet weak var EventImage: UIImageView!
#IBOutlet weak var type: UILabel!
#IBOutlet weak var rating: UIImageView!
#IBOutlet weak var date: UILabel!
#IBOutlet weak var name: UILabel!
}
class EventHistoryViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var TableView: UITableView!
let k = ["hi","hey","yo","hello"]
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return k.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.TableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! customCell
cell.name!.text = self.k[index.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
self.TableView.register(customCell.self, forCellReuseIdentifier: "cell")
TableView.delegate = self
TableView.dataSource = self
}
what am I doing wrong? the program in this case crashes when it reaches cell.name!.text = self.k[index.row]
Check this one
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: VC_FavouriteCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath) as! VC_FavouriteCell
cell.name.text = k[index.row]
return cell
}
In place of cell.name!.text write cell.name.text
Don't register the cell

Resources