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.
Related
SO I have two UICollectionViews in my UIViewController in Storyboard and both are linked with delegate and datasource to my ViewController. All the associated UICollectionView delegate methods are implemented and checks for the UICollectionViews are implemented. But it's so frustrating that one UICollectionView is getting catered while the other one is getting completely ignored. I have scratched my head in all the available aspects but it is kind of putting me further towards the edge, please help.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.variantsCollectionView {
// let count = (item?.variant_groups?.count)!
return 1
} else {
return 2//(item?.extra_groups?.count)!
}
}
and
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
if collectionView == self.variantsCollectionView {
//IT DOESNT EVEN COME HERE AT ALL
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell_variant", for: indexPath)
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
//HERE IT COMES ALWAYS FOR THE NUMBER OF CELLS
return cell
}
}
Whereas the UICollectionViews are connected like this:
and:
Please please help. Thank you so much
Via comments the TS found solution by following these steps:
Ensure both collection views have non-nil data sources (and delegates).
Check that data source methods are executed for both collection view.
Check that both collection views' cells have valid size.
Finally the problem was found after checking the heights of each collection view inside stack view.
basically CollectionView has a specific height whereas
VariantCollectionView didnt, and both were in a stackView. When first
was created in view it took up the entire size where as the other one
kind of actually disappeared. Hence the issue.
I've been successfully nesting collections views into tableview for a while now.
What I still don't know how to do, is to do it while respecting the MVC pattern?
Right now I declare my tableview and its cells and in the cell (where the collectionView sits), I attach my collectionView (I got 1 per cell) and do the data mapping. It works, but it's spaghetti code where my View is acting like a controller.
I tried a few times to respect the MVC patterns. I can get my controller to control both my tableview and my collection. Where I struggle is to tell my collection View delegate which data it should pick as all I have as reference is the indexPath (of the collectionView) but not in which tableView that specific collectionView sits.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
return cell
}
The delegate only gives me the indexPath of the cell not which collectionView it is. To use a concrete example - let's assume that my tableview cells represents messages and that each message has a collectionView that controls reactions (like Discord). How do I tell my collectionView delegate which Message it is linked to?
Thanks a lot for the help!
This is very simple. Every view and its subclass has a property tag. You must have an IBOutlet or a simple reference to the CollectionView that sits in the TableViewCell. When you dequeue the TableViewCell just set the tag of your CollectionView equal to the indexPath.row of your tableViewCell like this:
tableViewCell.collectionView.tag = indexPath.row
Then in the UICollectionViewDataSource or UICollectionViewDelegate methods you may find which collectionView it is in other words which tableViewCell does this collectionView sits in. Here's an example:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
let dataObject = dataSourceArray[indexPath.row] as! YourDataModelObject
// Here you may set any property of the collectionView cell
return cell
}
P.S: It is not necessary to respect MVC to that extent. You may manage your CollectionView from the TableViewCell. Have a look at this example.
I am working on an app where I'm stuck in a limbo between collection view cells and the contained table views within them.
My first collection view cell contains a table view with table view cells.
Each table view cell contain saved data and by selecting one cell two things should happen.
The collection view cell should change index to current +1
The table view cells data (in this case title and date) should be passed onto the new collection view cells header property.
One other aspect is that the table views are stored in a container view class. I'm not sure if this matter or not, but its one extra layer to pass the variables through.
So far this is where I get stuck
tableViewDidselectCode
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let header = LoadHeaderView()
let cell = tableView.dequeueReusableCell(forIndexPath: indexPath) as SavedTableViewCell
header.titleString = cell.taskLabel.text!
header.descriptionString = cell.clientLabel.text!
}
How do I pass this to
self -> ContainerView -> collectionCell[0] -> CollectionView -> collectionCell[1] -> tableView -> header?
Your table view has a dependency on the parent collection view cell. That means you need to pass a reference of the collection view cell to the table view on instantiation. I'd make a protocol.
protocol myTableViewCellDelegate {
func updateMyCollectionViewCell
}
extension myCollectionViewCell: myTableViewCellDelegate {
func updateMyCollectionViewCell {
// update whatever you need to here
}
}
extension myCollectionTableViewDelegate: UITableViewDelegate {
// ...
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath) {
// instantiate table view
// pass self to tableview *as weak reference*
myTableView.customDelegate = self
}
//...
}
extension myTableViewDelegate: UITableViewDelegate {
//...
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
// instantiate table view cell
// assuming you're not using custom cell class
tableCell.updateMyCollectionViewCell()
// if you are using custom cell class then the tableViewDelegate
// will need to pass collectionView reference on to the cells
}
}
Hope that helps!
I think you can use a blocks to send information and any other actions.
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
How can I achieve this screen with UITableViewCell and UITableViewController. With table section and header. Some ideas to achieve this?? Thanks!
What have you tried so far?
Your question seems a little broad.
You will need a set of custom UITableViewCell Subclasses, which you design in nibs.
To make the cells seem apart from each other, resize the content size of the Cells, and make the cell background another color.
Create a Segmented Control and add it to the Tableviews HeaderView.
For the FooterView it seems like this is some kind of subclassed Tabbar.
Easiest way to customise it in such a way, would be to create a View, and add buttons to it. Add this View as Subview to your TableViewController.
Have 2 UITableViewCell's one for each type i.e. 1 for showing the image and text and another for showing just the text.
Then in the cellForRowAt delegate method determine which to type to use based off the object you are data binding it to.
Example:
public final func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customObject = customObjects[indexPath.section]
switch customObject.type {
case .imageAndText:
if let cell = tableView.dequeueReusableCell(withIdentifier: ImageAndTextCell.identifier, for: indexPath) as? ImageAndTextCell {
cell.customObject = customObject
return cell
}
case .text:
if let cell = tableView.dequeueReusableCell(withIdentifier: TextCell.identifier, for: indexPath) as? TextCell {
cell.customObject = customObject
return cell
}
}
return UITableViewCell()
}
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).