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!!
Related
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
}
I have defined a class named c1 for a prototype cell like follows:
I defined the code for c1 like this:
class c1 : UITableViewCell {
public func configure(indexPath: IndexPath) -> UITableViewCell {
let place = places[indexPath.row]
self.textLabel?.text = place.name
self.detailTextLabel?.text = "\(place.timestamp)"
return self
}
}
Inside the UITableViewController following code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath)
// Configure the cell...
return cell.configure(indexPath);
}
But this works not because obv. the compiler doesn't know about c1: "Value of type 'UITableViewCell' has no member 'configure'"
And I don't understand Why: If I specify the classname "c1" in the storyboard, I expected that XCODE instantiate the appear. class automatically for the reusing mechanism.
So the tableView method should return the runtime instantiated "c1" class which is an child of UITableViewCell to have access to the "configure" method?
just cast the cell to your specific cell
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath) as? c1
and the job is done
Must cast the type of UITableview cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath) as? c1 // to inherit the method of class c1, need to cast
// Configure the cell...
return cell.configure(indexPath);
}
Please ⌥-click on dequeueReusableCell or read the documentation
func dequeueReusableCell(withIdentifier identifier: String, for indexPath: IndexPath) -> UITableViewCell
returns the base class UITableViewCell
If you declare a custom (sub-)class you have to cast the cell to the subclass. By the way please name the class with a starting capital letter.
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath) as! c1
And in configure you might have to return Self or c1
public func configure(indexPath: IndexPath) -> Self {
let place = places[indexPath.row]
self.textLabel?.text = place.name
self.detailTextLabel?.text = "\(place.timestamp)"
return self
}
And where does places come from in the cell? The configure method seems to be pointless.
To access the methods of your UITableViewCell you will have to cast the cell to that particular UITableViewCell
i.e
let cell = tableView.dequeueReusableCell(withIdentifier: "YOUR_IDENTIFIER") as? YOUR_CELL_CLASS
In case if you have the multiple UITableViewCell so you have to assign then a different identifier so based on that you can identify the particular cell. More or less there should an awareness which display your cell based on your data.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let aCellType = aCellData[indexPath.row].cellType //aCellData is the predefined data
switch aCellType {
case c1:
let cell = tableView.dequeueReusableCell(withIdentifier: "YOUR_IDENTIFIER") as? YOUR_CELL_CLASS_C1
cell.YOUR_METHOD_OF_THIS_CELL()
return cell
case c2:
let cell = tableView.dequeueReusableCell(withIdentifier: "YOUR_IDENTIFIER") as? YOUR_CELL_CLASS_C2
cell.YOUR_METHOD_OF_THIS_CELL()
return cell
case c3:
let cell = tableView.dequeueReusableCell(withIdentifier: "YOUR_IDENTIFIER") as? YOUR_CELL_CLASS_C2
cell.YOUR_METHOD_OF_THIS_CELL()
return cell
default:
debugPrint("That's all folks")
}
return UITableViewCell()
}
Hope this helps you!
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
In my view controller I want to have two collection views. After trying this, I got a Sigabrt Error and my app crashes. I am predicting that the problem is because I am assigning the datasource of these collection views to self. I can be wrong, here is my code:
In view did load, i set the datasource of the collection views:
#IBOutlet var hashtagCollectionView: UICollectionView!
#IBOutlet var createCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
createCollectionView.dataSource = self
hashtagCollectionView.dataSource = self
}
Then I create an Extension for the UICollectionViewDataSource
extension CategoryViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
var returnValue = 0
if collectionView == hashtagCollectionView {
// Return number of hashtags
returnValue = hashtags.count
}
if collectionView == createCollectionView {
// I only want 3 cells in the create collection view
returnValue = 3
}
return returnValue
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var return_cell: UICollectionViewCell
// Place content into hashtag cells
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "hashtagCell", for: indexPath) as! TrendingTagsCollectionViewCell
cell.hashtagText = hashtags[indexPath.row]
// Place content in creators cell
let createCell = collectionView.dequeueReusableCell(withReuseIdentifier: "createCell", for: indexPath) as! CreateCollectionViewCell
createCell.text = creators[indexPath.row]
createCell.image = creatorImages[indexPath.row]
// Is this the right logic?
if collectionView == hashtagCollectionView {
return_cell = cell
} else {
return_cell = createCell
}
return return_cell
}
}
This is crashing because your if statement in collectionView(_:cellForItemAt:) doesn't quite cover enough ground.
While you're right that you need to return a different cell based on what collection view is asking, you can't call dequeueReusableCell(withReuseIdentifier:for:) twice with different identifiers like that. Since you ask the same collection view both times, it's very likely that one of the identifiers isn't registered with that collection view.
Instead, you should expand the if to cover just about the entirety of that method:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == hashtagCollectionView {
// Place content into hashtag cells
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "hashtagCell", for: indexPath) as! TrendingTagsCollectionViewCell
cell.hashtagText = hashtags[indexPath.row]
return cell
} else if collectionView == createCollectionView {
// Place content in creators cell
let createCell = collectionView.dequeueReusableCell(withReuseIdentifier: "createCell", for: indexPath) as! CreateCollectionViewCell
createCell.text = creators[indexPath.row]
createCell.image = creatorImages[indexPath.row]
return cell
} else {
preconditionFailure("Unknown collection view!")
}
}
That only tries to dequeue a cell once, depending on which collection view is asking, and casts the returned cell to the right class.
N.B. This kind of approach works for awhile, but in the long run, you can get very long UICollectionViewDataSource method implementations, all wrapped up in a series of if statements. It might be worth considering separating out your data sources into separate smaller classes.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "hashtagCell", for: indexPath) as? TrendingTagsCollectionViewCell {
cell.hashtagText = hashtags[indexPath.row]
return cell
}
if let createCell = collectionView.dequeueReusableCell(withReuseIdentifier: "createCell", for: indexPath) as? CreateCollectionViewCell {
createCell.text = creators[indexPath.row]
createCell.image = creatorImages[indexPath.row]
return createCell
}
return UICollectionViewCell() // or throw error here
}
I think your code crash with a nil when reuse cell
You should test cellForItemAt like this
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == hashtagCollectionView {
// Place content into hashtag cells
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "hashtagCell", for: indexPath) as! TrendingTagsCollectionViewCell
cell.hashtagText = hashtags[indexPath.row]
return cell
} else {
// Place content in creators cell
let createCell = collectionView.dequeueReusableCell(withReuseIdentifier: "createCell", for: indexPath) as! CreateCollectionViewCell
createCell.text = creators[indexPath.row]
createCell.image = creatorImages[indexPath.row]
return createCell
}
}
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
}