I am working on a IOS Swift based project that uses a few classes to customize the UITableView and the UITableViewCell. Now one of my Cells inside the UITableView has an inner UITableView. I was wondering if it is possible when inside the cellForRowAtIndexPath, that I could also populate cells programmatically in that same process.
EX:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! CustomTableViewCell
...... do stuff
cell.detailview.uitableview <!-- populate the cells here?
.......
return cell
}
Suggestions?
Assuming three different types of cells:
class NormalTableViewCell: UITableViewCell: This is used for the "regular" cells of your outer (main) table view.
class TableContainingTableviewCell : UITableViewCell: This is used for the "special" cells of your outer (main) table view, that contain a table view (inner) within themselves.
class InnerTableViewCell : UITableViewCell: This is used for the cells of your inner table views (those contained in cells of class TableContainingTableviewCell).
(Replace each by your actual class names).
, you can use this code:
override func viewDidLoad()
{
super.viewDidLoad()
// This can also be done in storyboard with prototype cells:
self.tableView.registerClass(NormalTableViewCell.class, forCellReuseIdentifier: normalCellIdentifier)
self.tableView.registerClass(TableContainingTableViewCell.class, forCellReuseIdentifier: specialCellIdentifier)
}
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath
) -> UITableViewCell
{
if tableView == self.tableView {
// [A] OUTER TABLE VIEW
if indexPath == index path of table-containing cell {
// (A.1) TABLE-CONTAINING CELL
let cell = tableView.dequeueReusableCellWithIdentifier(specialCellIdentifier, forIndexPath: indexPath) as! TableContainingTableViewCell
// (...configure cell...)
// Setup and refresh inner table view:
cell.contentView.tableView.dataSource = self
// This is needed for dequeueing to succeed:
cell.contentView.tableView.registerClass(InnerTableViewCell.class, forCellReuseIdentifier: innerCellIdentifier)
cell.contentView.tableView.reloadData()
// ^ THIS TRIGGERS A CALL TO THIS FUNCTION, ON THE
// INNER TABLE VIEW (PATH [B] BELOW)
return cell
}
else {
// (A.2) NORMAL CELL
let cell = tableView.dequeueReusableCellWithIdentifier(normalCellIdentifier, forIndexPath: indexPath) as! NormalTableViewCell
// (configure cell)
return cell
}
}
else {
// [B] INNER TABLE VIEW
let cell = tableView.dequeueReusableCellWithIdentifier(innerCellIdentifier, forIndexPath: indexPath) as! InnerTableViewCell
// (configure cell)
return cell
}
}
...but I would strongly argue against having a table view embedded inside a another table view's cell. At the very least, make sure the inner table view does not need to scroll (i.e., the containing cell is high enough to show all rows and the table itself has scroll disabled).
Related
The Issue
I am attempting to use a collection view in a view controller for cards. When a user taps on a card, it expands. At all times, I have a tableview in each card, whether it is expanded or not. I have the data loading in the table views, but only when I tap on a card to expand it or if I scroll collection view cards offscreen and back onscreen. What is a cleaner workflow to doing this that puts tableviews in each collection view cell?
This is in my main view controller:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "productionBinderCell", for: indexPath) as? ProductionBinderCollectionViewCell
let suck = cell?.detailsTableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? DescriptionTableViewCell
suck?.descriptionText.text = productions[indexPath.row].productionDescription
cell?.detailsTableView.reloadData()
cell?.layer.shouldRasterize = true
cell?.layer.rasterizationScale = UIScreen.main.scale
let title = productions[indexPath.row].productionTitle
let luck = productions[indexPath.row].productionImage
let zuck = UIImage(data:luck!)
cell?.productionTitle.text = title
cell?.productionFeaturedImage.image = zuck
cell?.productionColorTint.tintColor = UIColor.blue
let backbtn = cell?.viewWithTag(1)
backbtn?.isHidden = true
return cell!
}
This is in a subclass of the tableview:
override func layoutSubviews() {
super.layoutSubviews()
self.dataSource = self
self.delegate = self
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "productionDescription", for: indexPath) as? DescriptionTableViewCell
return cell!
}
This is in a subclass of the tableview cell I am concerned with. It only shows the uilabel text when scrolling offscreen or tapping on the collection view cell:
class DescriptionTableViewCell: UITableViewCell {
#IBOutlet var descriptionText: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Maybe I shouldn't be using a tableview at all? Thank you very much to all who help. I am very open to criticism and love learning from my mistakes (a little too often). :)
What you need, first of all, is clear definition of views and cells.
UICollectionview is a collection of UICollectionViewCell objects
UITableView is a collection of UITableViewCell objects
Do you need to embed entire table view within each card? There you have your answer. You have parent-child relationship between your UICollectionViewCell and UITableView derived classes:
Derive from UICollectionViewCell - MyCollectionViewCell - this step is mandatory.
Inside xib for MyCollectionViewCell, insert a tableview, with horizontal scrolling enabled. (this is an option inside xib attribute editor)
If needed to customize, also derive from UITableViewCell - MyTableViewCell. This step is optional.
Inside MyCollectionViewCell class file, have implementation for table view datasource and delegate methods. That way, each card will have all its table view cell children in one place
If present, define custom logic inside MyTableViewCell class file
There is no need to subclass UITableView here. Everything that needs to be customized can be done using subclassing cells.
I have 3 collection views on 1 view controller. I've tried a few of the other suggestions I've found on Stack but nothing seems to work.
All 3 Collection Views are in a separate cell of a HomeTableViewController. I tried to create the outlet connection to the HomeTableViewController but I get the error Outlets cannot be connected to repeating content.
I've read many people being able to hook up their multiple collectionViews so I am a bit confused as to where I'm going wrong...
The UICollectionView instances cannot be hooked up to IBOutlet properties in a separate UITableViewController.
As you describe, the UICollectionViews are actually each children of their own parent UITableViewCell, and as such are not direct descendants of the UITableViewController. This is because the cells will be added to the UITableView at run time.
If you are set on creating the outlets within the HomeTableViewController I would suggest creating them like so:
private weak var collectionViewA: UICollectionView?
and overriding cellForRow like so:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
// cast cell as collection view parent
collectionViewA = cell.collectionView
return cell
}
As has been mentioned, the better solution would be to manage the UICollectionView instances from within their own UITableViewCell parents. Example:
final class CollectionViewAParentTableViewCell: UITableViewCell {
#IBOutlet private weak var collectionView: UICollectionView!
}
extension CollectionViewAParentTableViewCell: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
…
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
…
}
}
You should create outlet in the UITableViewCell. Then you can provide tags to collectionViews in each cell in tableView:cellForRowAtIndexPath Method:
yourCell.collectionViewOutlet.tag = indexPath.row + 1000
you should replace 1000 with Any Constant Integer if the tags conflict with tags of other views.
Then use these tags to differentiate all collectionviews in collectionview:cellForItemForIndexpath method:
if(collectionView.tag == 1000){
//Code for collection view in first row of the table
}
else if(collectionView.tag == 1001){
//Code for collection view in second row of the table
}
else if(collectionView.tag == 1002){
//Code for collection view in third row of the table
}
You should also keep in mind to return number of items for each collection view just like above.
Tags make the life whole lot easier , don't they?
Happy Coding (Y)
You should create outlet in the UITableViewCell. Then you can provide tags to collectionViews in each cell in tableView:cellForRowAtIndexPath Method:
yourCell.collectionViewOutlet.tag = indexPath.row + 1000
you should replace 1000 with Any Constant Integer if the tags conflict with tags of other views.
Then use these tags to differentiate all collectionviews in collectionview:cellForItemForIndexpath method:
if(collectionView.tag == 1000){
//Code for collection view in first row of the table
}
else if(collectionView.tag == 1001){
//Code for collection view in second row of the table
}
else if(collectionView.tag == 1002){
//Code for collection view in third row of the table
}
You should also keep in mind to return number of items in collectionView:numberOfItemsInSection for each collection view just like above.
Tags make the life whole lot easier , don't they?
Happy Coding (Y)
Create three different collection view with different collection view cell, then after you just need to add in dataSource method like below:-
if collectionView == collectionViewA{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellA", for: indexPath) as! collectionCell
return cell
}else if collectionView == collectionViewB{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellB", for: indexPath) as! collectionCell
return cell
}else if collectionView == collectionViewC{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellC", for: indexPath) as! collectionCell
return cell
}else{
return UICOllectionViewCell()
}
also perform same for other dataSource method.
Currently, I have two table view controllers which contain some identical custom tableview cells. Each table view controller has its own custom tableview cells. I'm wishing to create only one custom table view cell which can be shared among these two table view controllers. Any guides can I refer to?
First you can create a UITableViewCell with code: (and you can design your cell view in a .xib file - see here for detail)
// MyCell.Swift
import UIKit
import Foundation
class MyCell: UITableViewCell {
// do whatever you want to customize the cell
}
Then in each of your two UITableViewController's classes, register your custom UITableViewCell class in viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(MyCell.self as AnyClass, forCellReuseIdentifier: "cell")
}
Then create/reuse this custom cell in cellForRowAtIndexPath function:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: MyCell? = self.tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? MyCell
if cell == nil {
// if the cell is not yet created in the system, create a new cell object
cell = MyCell(style: .default, reuseIdentifier: "cell")
}
// customize your cell...
cell.textLabel?.text = "Your Label Text"
return cell!
}
I have a UITableView with a transparent background color and cells which also have a transparent background color. When I reload my tableView with:
dataSource = some new data
tableView.reloadData()
I can see the new cells overlap the old ones.
I did try to use use
tableView.beginUpdates()
// remove all rows here
change data source
// insert new rows here
tableView.endUpdates()
but it did not work. I tried as well tableView.reloadRowsAtIndexPath(...) but still no luck.
And finally I set all my cells and my table view to clear graphic context when redrawn but it did not manage to fix this issue.
Any help would be greatly appreciated.
My cell creation function:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("suggestioncell")
cell.backgroundColor = UIColor.whiteColor().alpha(0.1)
cell.textLabel?.text = (suggestions![indexPath.row] as! SVPlacemark).formattedAddress
cell.clearsContextBeforeDrawing = true
cell.contentView.clearsContextBeforeDrawing = true
return cell
}
Try overriding prepareForReuse in you UITableViewCell subclass, and reset content there.
Here's what the documentation says about that:
Prepares a reusable cell for reuse by the table view's delegate.
If a UITableViewCell object is reusable—that is, it has a reuse identifier—this method is invoked just before the object is returned from the UITableView method dequeueReusableCellWithIdentifier:. For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state. The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell. If the cell object does not have an associated reuse identifier, this method is not called. If you override this method, you must be sure to invoke the superclass implementation.
Custom UITableViewCell class:
class customCell: UITableViewCell {
override func prepareForReuse() {
self.textLabel?.text = nil
}
}
In your cellForRowAtIndexPath method:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("suggestionCell", forIndexPath: indexPath) as! customCell
cell.backgroundColor = UIColor.whiteColor().alpha(0.1)
cell.textLabel?.text = (suggestions![indexPath.row] as! SVPlacemark).formattedAddress
return cell
}
And, of course in your XIB/Storyboard, set the cell class to CustomCell, and set its reuse identifier.
I'm having an issue where my app will freeze if when I press it to got the next scene which is a view controller with a tableview nested inside of it. the app will only freeze when I've declared how many rows the table will have. If anyone knows how to fix this problem its be greatly appreciated.
class ViewController: UIViewController, UITableViewDataSource {
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell()
cell.textLabel?.text = "hello"
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Have you set dataSource of your table view? You must call tableView.dataSource = self somewhere in your code (most probably in viewDidLoad method)
The problem with your code is that in tableView(_:cellForRowAtIndexPath:) you are creating new cell every time the method is called. That is not how table views (or collection views) work.
The idea behind these components is to reuse their cells so that your application shows better performance. What if you have 1000 cells? You will create 1000 UITableViewCell objects for each cell? This is where dequeueReusableCellWithIdentifier(_:) comes in handy.
So set an identifier for your cell in Interface Builder, for example "Cell" and get the cell like this:
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
This is the right way to go.
You have to make sure you set the correct reuse identifier for your table view cell (either in storyboard, or code).
Storyboard:
Of if you want to do it in code, put this in viewDidLoad():
tableView.registerClass(UITableViewCell, forHeaderFooterViewReuseIdentifier: "cellIdentifier")
Important to know that you should only use one of the above. If you define your UI in Storyboards, I recommend the first approach.
Then when you create the cell in tableView(_:, cellForRowAtIndexPath), instead of this:
var cell = UITableViewCell()
Use a cell from the reuse queue like so:
var cell = tableView.dequeueReusableCellWithIdentifier("cellIdentifier", forIndexPath: indexPath) as! UITableViewCell
Everytime you are creating object for cell, make sure you use reuse identifier.
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell