App Scheme
As you can see from the scheme I have UITableView and in UITableViewCells I have UICollectionView. The problem I face is that I can't get correct UITableViewCell number when I set text on labels in my UICollectionView
Here's how I'm trying to check which row of the UITableView currently there's:
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ExploreTableViewCell
cell.currentTableViewRow = indexPath.item
return cell
}
}
And then I set the label values according to the currentTableViewRow variable which I created:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ExploreCollectionViewCell
switch(currentTableViewRow) {
case 1:
cell.collectionTitle.text = Const.newCollection.subCollections[indexPath.item]
case 2:
cell.collectionTitle.text = Const.freeCollection.subCollections[indexPath.item]
case 3:
cell.collectionTitle.text = Const.mostLikedCollection.subCollections[indexPath.item]
case 4:
cell.collectionTitle.text = Const.healthFitnessCollection.subCollections[indexPath.item]
case 5:
cell.collectionTitle.text = Const.earthCollection.subCollections[indexPath.item]
default:
cell.collectionTitle.text = Const.autumnEditionCollection.subCollections[indexPath.item]
}
return cell
}
Also, I reloadData() of my UITableView in my viewDidAppear()
I think the problem is that cells are reusable and that's why first 4 rows of my UITableView are correct and the other ones not. Any suggestions?
Add function inside UITableViewCell :
func reloadCollectionView() -> Void {
self.collectionView.reloadData()
}
And then use it like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ExploreTableViewCell
cell.currentTableViewRow = indexPath.item
cell.reloadCollectionView()
return cell
}
Related
I am making a form with a tableView with multiple kind of cell types. One of these types contains an UICollectionView with buttons to select some answers.
What I need is to be able to hide or show rows in the table, regarding on the answers.
For example: when answer to question 2 is "No", question 3 is not showing anymore.
But I don't know how to make that tableview knows what's being selected in one of it's cells
In the cell that contains the UICollectionView I have this method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let indexPath = optionsCollectionView.indexPathsForSelectedItems?.first
let cell = collectionView.cellForItem(at: indexPath!) as! OptionsCollectionViewCell
let data = cell.itemButton.titleLabel?.text
selectedItem = data
}
But I don't know how to automatically pass it to the tableview so it knows what rows to show or hide... Any idea?
this is my cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if sortedFixedContentType.count != 0 {
let item = sortedFixedContentType[indexPath.row]
switch item.typeId {
case "42":
let cell = tableView.dequeueReusableCell(withIdentifier: "FormFileCell", for: indexPath) as! FormFileCell
return cell;
case "39":
let cell = tableView.dequeueReusableCell(withIdentifier: "FormExpenseTypeCell", for: indexPath) as! FormExpenseTypeCell
return cell;
case "86":
let cell = tableView.dequeueReusableCell(withIdentifier: "FormExpensePaymentModeCell", for: indexPath) as! FormExpensePaymentModeCell
return cell;
case "87":
let cell = tableView.dequeueReusableCell(withIdentifier: "FormExpenseFileTypeCell", for: indexPath) as! FormExpenseFileTypeCell
return cell;
case "88":
let cell = tableView.dequeueReusableCell(withIdentifier: "FormProviderCell", for: indexPath) as! FormProviderCell
return cell;
default:
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! BaseFormCell
cell.idLabel.text = item.htmlType
return cell
}
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! BaseFormCell
cell.idLabel.text = ""
return cell
}
}
Thanks
There are Four steps to do this :-
1.First create a protocol lets say 'CustomCellDelegate' in your custom cell where you are using collectionview inside and create a variable that will hold the custom delegate just to give a example suppose your cell name is CustomCell create a CustomCellDelegate and declare it as customDelegate
protocol CustomCellDelegate : class {
func Method1()
}
class CustomCell : UITableViewCell {
var customDelegate : CustomCellDelegate?
}
2.Then you need to fire that delegate from CustomCell class collectionview delegate method didSelectItemAt like this
extension CustomCell : UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let delegate = customDelegate {
delegate.Method1()
}
}
}
3.Third assign the customDelegate to the view controller where you want to get the delegate for example myViewController here
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customeCellIdentifier", for: indexPath) as! CustomCell
cell.customDelegate = self // myViewController
return cell
}
4.Handle the delegate in your view controller like this
extension myViewController : CustomCellDelegate {
func Method1() {
print("Method 1 called")
}
}
I Hope this will solve your problem let me know if you find this answer helpful. Cheers!!
I am trying to perform a segue from a collection view which is inside a table view cell, the view looks like this
enter image description here
To explain it well, I have a table view, each row has a cell , inside the second row , I have a collection view which includes several products
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "HeaderCell", for: indexPath)
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemTitle", for: indexPath)
return cell
case 2:
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath)
return cell
case 3:
let cell = tableView.dequeueReusableCell(withIdentifier: "SectionTitle", for: indexPath)
return cell
case 4:
let cell = tableView.dequeueReusableCell(withIdentifier: "CollectionCell", for: indexPath) as! CollectionCell
return cell
default:
let cell = tableView.dequeueReusableCell(withIdentifier: "CollectionCell", for: indexPath) as! CollectionCell
return cell
}
}
Inside my ItemCell class I have the following
#IBOutlet weak var collectionView: UICollectionView!
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "singleItemCell", for: indexPath) as! singleItemCell
cell.itemImage.image = images[indexPath.row]
cell.itemImage.contentMode = .scaleAspectFit
return cell
}
I want to perform a segue such as when the user select a cell from the collection view, It takes him to another view controller. How can I do that ?
I tried performSegue but it does not allow me since I am in the cell class
Please any tips ?
In Your ItemCell class make a variable for referencing your ViewController like this:
var productVC:UIVIewController?
while loading TableView Cell set the reference to self like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 2:
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath)
cell.productVC = self
return cell
default:
let cell = tableView.dequeueReusableCell(withIdentifier: "CollectionCell", for: indexPath) as! CollectionCell
return cell
}
}
then implement the didSelect UICollectionView delegate method in ItemCell Class
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
productVC.performSegue(withIdentifier: "pass your segue identifier here..", sender: nil)
}
protocol ProductCellDelegate: class {
func didChoosedProduct(item: ItemImage)
}
class ProductCell {
#IBOutlet weak var collectionView: UICollectionView!
// Your Model Array
var itemImages: [ItemImages] = [] {
didSet {
self.collectionView.reloadData()
}
// Must set the delegate to your viewcontroller object in tableviewcell cell for rowat indexpath method
weak var delegate: ProductCellDelegate?
override func awakeFromNib() {
self.collectionView.delegate = self
}
}
extension ProductCell: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedItem = self.itemImages[indexPath.row]
self.delegate?.didChoosedProduct(item: selectedItem)
}
}
Implement ProductCellDelegate in your viewcontroller. Call performsegue to navigation.
Implement UICollectionViewDataSource and UICollectionViewDelegate not in tableView cell, but in your ViewController.
You can access to collectionView in cellForRowAtIndexPath method, so you can assign it dataSource and delegate to self
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "HeaderCell", for: indexPath)
return cell
case 1:
if let cell = tableView.dequeueReusableCell(withIdentifier: "ItemTitle", for: indexPath) as? ItemCell {
cell.collectionView?.delegate = self
cell.collectionView?.dataSource = self
return cell
}
...
}
}
And from ViewController you can make performSegue method
I have a UITableView that has one UICollectionView and
I used numberOfRowsInSection to set the number of rows and cellForRowAt to show cells with title:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return sectionsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let strname = sectionsArray[indexPath.row]
if (strname as AnyObject).isEqual(to: "top"){
tableCellobj = tableView.dequeueReusableCell(withIdentifier: "cellmiddle", for: indexPath) as! TableCellMiddle
tableCellobj.lblMiddle.text = "\(strname)"
return tableCellobj
}
if (strname as AnyObject).isEqual(to: "bottom") {
tableCellobj = tableView.dequeueReusableCell(withIdentifier: "cellmiddle", for: indexPath) as! TableCellMiddle
tableCellobj.lblMiddle.text = "\(strname)"
return tableCellobj
}
As you can see the Array sectionsArray, is:
var sectionsArray: NSMutableArray = NSMutableArray()
sectionsArray.add("top")
sectionsArray.add("bottom")
Now the table shows two rows with headers "top" and "bottom"
I have two diffrent data as NSArray to be shown in the CollectionView:
top = ["1","2","3"]
bottom = ["4","5","6"]
the collectionView has a tag (identified in Storyboard), so i used the tag to defined the view and show data:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
if collectionView.tag == 2 {
objCollectionViewCellMiddle = collectionView.dequeueReusableCell(withReuseIdentifier: "collectioncellmiddle"
, for: indexPath) as! CollectionViewCellmiddle
objCollectionViewCellMiddle.lblMiddle.text = top[indexPath.row] as? String
return objCollectionViewCellMiddle
}
Now, the TableView shows two CollectionViews, but it prints same data from top array in both CollectionView
I want:
1st CollectionView ---> show the top array
2nd CollectionView ---> show the bottom array
I played around with indexPath.section, but couldn't figure away to make it work as it always prints "0"
Okay, I think what we need to do here is change around how you're setting things up. First off, we should change the backing data source to be a value type such as [String]:
MyViewController Class
class MyViewController: UIViewController {
var sectionsArray: [String] = ["top", "bottom"]
var topSection: [String] = ["1","2","3"]
var bottomSection: [String] = ["4","5","6"]
#IBOutlet weak var myTableView: UITableView!
#IBOutlet weak var topCollectionView: UICollectionView!
#IBOutlet weak var bottomCollectionView: UICollectionView!
}
Then, we need to modify the data sources for the table view and collection views:
Table View Data Source
extension MyViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return sectionsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sectionName = sectionsArray[indexPath.section]
switch sectionName {
case "top":
let tableCell = tableView.dequeueReusableCell(withIdentifier: "cellmiddle", for: indexPath) as! TableCellMiddle
tableCell.lblMiddle.text = sectionName
return tableCell
case "bottom":
let tableCell = tableView.dequeueReusableCell(withIdentifier: "cellmiddle", for: indexPath) as! TableCellMiddle
tableCell.lblMiddle.text = sectionName
return tableCell
default:
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return topSection.count
case 1:
return bottomSection.count
default:
return 0
}
}
}
Here, in the cellForRow method you can actually reduce some of the duplicate code as you're doing the same thing regardless of which cell you're at.
Collection View Data Source
extension MyViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch collectionView {
case topCollectionView:
return topSection.count
case bottomCollectionView:
return bottomSection.count
default:
return 0
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch collectionView {
case topCollectionView:
let collectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectioncellmiddle", for: indexPath) as! CollectionViewCellmiddle
collectionCell.lblMiddle.text = topSection[indexPath.row]
return collectionCell
case bottomCollectionView:
let collectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectioncellmiddle", for: indexPath) as! CollectionViewCellmiddle
collectionCell.lblMiddle.text = bottomSection[indexPath.row]
return collectionCell
default:
return UICollectionViewCell()
}
}
}
Here what we're doing is switching on the actual collection view itself that gets passed in as an argument rather than a tag that you set, that way we can easily see which collection view is currently being accessed. Once we know this we are able to determine which of your backing arrays we need to access and can assign the text accordingly.
You can also reduce some of the duplicate logic in this method if you'd like.
This is a playground that compiles (I will add functionality later to demonstrate it so it shows up in the timeline). I think it will help you achieve what you're trying to do.
Well I feel the problem is in this function:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
if collectionView.tag == 2 {
objCollectionViewCellMiddle = collectionView.dequeueReusableCell(withReuseIdentifier: "collectioncellmiddle"
, for: indexPath) as! CollectionViewCellmiddle
objCollectionViewCellMiddle.lblMiddle.text = top[indexPath.row] as? String
return objCollectionViewCellMiddle
}
}
The data assignment shows:
objCollectionViewCellMiddle.lblMiddle.text = top[indexPath.row] as?
String
I suggest you change the tableview code by adding the tag code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let strname = sectionsArray[indexPath.row]
if (strname as AnyObject).isEqual(to: "top"){
tableCellobj = tableView.dequeueReusableCell(withIdentifier: "cellmiddle", for: indexPath) as! TableCellMiddle
Add this: tableCellobj.collectionView.tag = 1
tableCellobj.lblMiddle.text = "\(strname)"
return tableCellobj
}
if (strname as AnyObject).isEqual(to: "bottom") {
tableCellobj = tableView.dequeueReusableCell(withIdentifier: "cellmiddle", for: indexPath) as! TableCellMiddle
Add this: tableCellobj.collectionView.tag = 2
tableCellobj.lblMiddle.text = "\(strname)"
return tableCellobj
}
}
And then:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
if collectionView.tag == 1 {
objCollectionViewCellMiddle = collectionView.dequeueReusableCell(withReuseIdentifier: "collectioncellmiddle"
, for: indexPath) as! CollectionViewCellmiddle
objCollectionViewCellMiddle.lblMiddle.text = top[indexPath.row] as? String
return objCollectionViewCellMiddle
}
else {
objCollectionViewCellMiddle = collectionView.dequeueReusableCell(withReuseIdentifier: "collectioncellmiddle"
, for: indexPath) as! CollectionViewCellmiddle
objCollectionViewCellMiddle.lblMiddle.text = bottom[indexPath.row] as? String
return objCollectionViewCellMiddle
}
}
I think I'm missing something fundamental to Swift, but can't find other examples of people doing what I'm trying:
Background
I have a UITableView with 2 prototype cells, with a different identifies, different features (label, image etc) and different classes.
I want the cellForRowAt function to return a different type and class of cell depending on the content in the array that holds the table data. That array is populated with struct instances, one characteristic of which identifies the type of cell that I want to represent the data.
Code Attempt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch dataArray[indexPath.item].typeOfData {
case "Type1":
let cell = tableView.dequeueReusableCell(withIdentifier: "type1ReuseIdentifier", for: indexPath) as! type1Cell
//Set up the cell contents
return cell
case "Type2":
let cell = tableView.dequeueReusableCell(withIdentifier: "type2ReuseIdentifier", for: indexPath) as! type2Cell
//Set up the cell contents
return cell
default:
let cell = tableView.dequeueReusableCell(withIdentifier: "separatorIdentifier", for: indexPath) as! separatorCell
//Set up the cell contents
cell
}
}
What am I missing / doing wrong?
Edit:
It was missing the final return.
just do like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let typeSection = CellType(rawValue: indexPath.row)!
switch typeSection {
case .CenterTitleCell:
return getCenterTitleCell(tableView, indexPath)
case .CenterDescriptionCell:
return getCenterDescriptionCell(tableView, indexPath)
case .CenterImageCell:
return getImageCell(tableView, indexPath)
case .FooterTitleCell:
return getFooterViewCell(tableView, indexPath)
}
}
And use another method to return the Cell Type
func getCenterTitleCell (_ tableView: UITableView, _ indexPath:IndexPath) -> CenterTitleTableViewCell {
let cell:CenterTitleTableViewCell = tableView.dequeueReusableCell(withIdentifier: String(describing: CenterTitleTableViewCell.self),
for: indexPath) as! CenterTitleTableViewCell
cell.selectionStyle = .none
return cell
}
This may be a little confusing, but i have a view controller that has a table view that contains a custom table cell and within this table cell, I have a collectionView with each cell holding a single image. I am trying to configure my collectionCell's image using the userToView variable seen below.
The delegate function below is where the table cell is being configured:
OtherUserAccountViewController.swift
class OtherUserAccountViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var userToView = User()
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:OtherUserPhotosTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: photosCellReuseIdentifier) as! OtherUserPhotosTableViewCell
cell.selectionStyle = .none
return cell
}
...
}
The delegate function below is where this image is being set:
OtherUserPhotosTableViewCell.swift
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "otherUserPhotosCollectionCell", for: indexPath) as! OtherUserPhotosCollectionViewCell
//Configure cell with photo from parent ViewController
return cell
}
My custom CollectionViewCell can be seen below:
OtherUserPhotosCollectionViewCell.swift
class OtherUserPhotosCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
}
I have only really been getting accustomed to Swift & iOS development over the last couple weeks so any help is much appreciated. Thank you for your time!
Answered by assuming that you have UIImages already available to you
Assuming you have a single UICollectionViewCell
In OtherUserPhotosTableViewCell.swift
var photoFromTableView: UIImage?
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "otherUserPhotosCollectionCell", for: indexPath) as! OtherUserPhotosCollectionViewCell
//Configure cell with photo from parent ViewController
if let photo:UIImage = photoFromTableView {
cell.imageView.image = photo
}
return cell
}
In OtherUserAccountViewController.swift
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:OtherUserPhotosTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: photosCellReuseIdentifier) as! OtherUserPhotosTableViewCell
cell.selectionStyle = .none
cell.photoFromTableView = //Image you want to pass
return cell
}
Assuming you have multiple UICollectionViewCells
In OtherUserPhotosTableViewCell.swift
var photosArray: [UIImage]?
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "otherUserPhotosCollectionCell", for: indexPath) as! OtherUserPhotosCollectionViewCell
//Configure cell with photo from parent ViewController
if let photo_array:[UIImage] = photosArray {
cell.imageView.image = photo_array[IndexPath.row]
}
return cell
}
In OtherUserAccountViewController.swift
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:OtherUserPhotosTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: photosCellReuseIdentifier) as! OtherUserPhotosTableViewCell
cell.selectionStyle = .none
cell.photosArray = //Array of Images you want to pass
return cell
}