How to get indexPath in cell class? - ios

I'm trying to get the indexPath of a tableView cell in the cell class.
I've got a collectionView inside the table view cells and I'm trying to make label inside the collectionView cell show the indexPath.row of the tableView cell that particular collectionView in.
Currently i have
var indexPathForCell: indexPath
in my cell class.
Then in the tableView class i have this in cellForRowAt indexpath
cell.indexpathForCell = indexPath
lbl.text = String(indexPathForCell.row)
If there is "3" or fewer tableView cells this works but if theres more then the 4th row then shows "0" as the indexPathForcell.row, and as I scroll in the collectionView the numbers then chnage from "0" to "3" and even show "1". Each cell then shows a different number as i scroll.

a simple solution for that is, in your viewController inside the collection cellForItemAt method gives the cell a tag like
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SignupSelectCourseCollectionViewCell", for: indexPath) as! SignupSelectCourseCollectionViewCell
cell.tag = indexPath.row
return cell
}
and now you can access tag property in cell class like
override var isSelected: Bool {
didSet {
let indexPathRow = self.tag
}
}

Related

How to have custom cell in UICollectionView

I have a UICollectionView that I want to make 3 custom cells appear.
I have read the documentation but I haven't been able to fix this issue.
Is there something I am missing?
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
I have tried changing the return 1 to 3 to make 3 custom cells appear but it only makes the 1st custom cell appear 3 times.
I have created a video and linked the video below explaining my situation.
https://www.loom.com/share/9b5802d6cc7b4f9a93c55b4cf7d435bb
Edit I have used #Asad Farooq method and it seems to have worked for me. I added my CollectionView's shown below and I can now make custom cells!
if(indexPath.item==0)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DailyCollectionViewCell", for: indexPath) as! DailyCollectionViewCell
return cell
}
if(indexPath.item==1)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WeeklyCollectionViewCell", for: indexPath) as! WeeklyCollectionViewCell
return cell
}
else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MonthlyCollectionViewCell", for: indexPath) as! MonthlyCollectionViewCell
return cell
}
}
As we can see from the documentation of Apple,
You typically don’t create instances of this class yourself. Instead, you register your specific cell subclass (or a nib file containing a configured instance of your class) using a cell registration. When you want a new instance of your cell class, call the dequeueConfiguredReusableCell(using:for:item:) method of the collection view object to retrieve one.
We have to register the cell to the collectionView before using it, for example:
class CustomCollectionViewCell: UICollectionViewCell {
// my custom collection view cell
}
Then we gonna register it to the collection view:
class MyViewController: UIViewController {
...
override func viewDidLoad(){
super.viewDidLoad()
...
self.myCollectionView.dataSource = self
// register the cells, so the collectionView will "know" which cell you are referring to.
self.myCollectionView.register(UINib(nibName: "CustomCollectionViewCell", bundle: nil), forCellReuseIdentifier: "customReuseIdentifier")
// register all type of cell you wanted to show.
}
}
extension MyViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// return number of cell you wanted to show, based on your data model
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = routineCollectionView.dequeueReusableCell(withReuseIdentifier: "customReuseIdentifier", for: indexPath) as! CustomCollectionViewCell
// cast the cell as CustomCollectionViewCell to access any property you set inside the custom cell.
// dequeue cell by the reuseIdentifier, "explain" to the collectionView which cell you are talking about.
return cell
}
}
The above code snippet is just a brief example, but I hope that explain the idea.
If you got multiple type of custom cell, you'll have to create classes for them (sub-class of UICollectionViewCell), register them to your collectionView, and dequeue them in collectionView(cellForRowAt:).
There are plenty of tutorial on the internet, here share one of my favourite:
https://www.raywenderlich.com/9334-uicollectionview-tutorial-getting-started
Edit:
If you are using storyboard only to add your custom collectionViewCell, you don't need to register the cell again, the cell already existed in the collectionView (Sorry the above code is just my preference). Just set the class & identifier of the cell, and dequeue the cell using the identifier in collectionView(cellForRowAt:).
We have to register the three different custom cell to the collectionView before using it then inside this function add this code
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if(indexPath.item==0)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell1", for: indexPath) as! cell1
return cell1
}
if(indexPath.item==1)
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell2", for: indexPath) as! cell2
return cell2
}
else
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell3", for: indexPath) as! cell3
return cell3
}

UICollectionView - random cells are selected

I have a Horizontal UICollectionView like the horizontal Calender in iOS.
Paging is enabled but not allowsMultipleSelection.
self.allowsMultipleSelection = false
self.isPagingEnabled = true
There are only 5 cells per page.
let cellSize = CGSize(width: self.view.frame.width / 5 , height: 60)
CollectionView's height is also 60.
didSelectItemAt change background color to .red and didDeselectItem resets it to .white.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if let cell = cell {
cell.backgroundColor = .red
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if let cell = cell {
cell.backgroundColor = .white
}
}
The collection view has multiple sections and rows. If I select a cell in the first visible page and scroll, random cells are selected in the next visible pages. That is to say random cells are red in the next pages. I do not want this to be so. I want to select/change color of cells manually.
How can I fix this?
Don't forget that UICollectionView has embedded reusing mechanism, so you should deselect your cells in the method "prepareToReuse" directly inside the cell class.
Take a class-level variable, say index
var index = -1
As you have said that multiple selections are not allowed so the following will do the job for you
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
index = indexPath.item
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if let cell = cell {
cell.backgroundColor = indexPath.item == index ? .red : .white
}
}
Whenever user tap on any cell we save the position in index variable and then call the reloadData() to notify collectionView about the change
In cellForRowAt we check if the current cell us selected we set the color to red otherwise white
First, if you want to preserve multiple selection, you have to remember your selected ones in an array since it would get lost if a cell gets recycled and reused. For that use something like a [IndexPath] type). If one selected cell is enough, you could use a non-array version of below code.
var selectedItems: [IndexPath] = []
Then, do your recoloring in your cell's cellForItemAt(:):
cell.backgroundColor = selectedItems.contains(indexPath) ? .red : .white
Your didSelectItemAt delegate function should look like:
if !selectedItems.contains(indexPath) { selectedItems.append(indexPath)}
collectionView.cellForItem(at: indexPath)?.backgroundColor = .red
and your didDeselectItemAt delegate function:
if let index = selectedItems.firstIndex(of: indexPath) { selectedItems.remove(at: index) }
collectionView.cellForItem(at: indexPath)?.backgroundColor = .white
This should actually work. Let me know if we have to do adjustments.

UIcollectionView weird cell recycling behaviour

I have a UICollectionView with flow layout, about 140 cells each with a simple UITextView. When a cell is recycled, I pop the textView onto a cache and reuse it later on a new cell. All works well until I reach the bottom and scroll back up. At that point I can see that the CollectionView vends cell number 85, but then before cell 85 is displayed it recycles it again for cell 87 so I now lose the content of the cell I had just prepared.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FormCell", for: indexPath) as! FormCollectionViewCell
let textView = Cache.vendTextView()
textView.text = "\(indexPath.row)"
cell.addSubview(textView)
cell.textView = textView
return cell
}
And on the UIcollectionViewCelC
override func prepareForReuse() {
super.prepareForRuse()
self.textView.removeFromSuperView()
Cache.returnView(self.textView)
}
I would have thought that after cellForItemAtIndexPath() was called, it would then be removed from the reusable pool of cells but it seems it is immediately being recycled again for a neighbouring cell. maybe a bug or I am possibly misunderstanding the normal behaviour of UICollectionView?
As I understand it, what you're trying to do is just keep track of cell content - save it when cell disappears and restore it when it comes back again. What you're doing can't work well for couple of reasons:
vendTextView and returnView don't take indexPath as parameter - your cache is storing something and fetching something, but you have no way of knowing you're storing/fetching it for a correct cell
There's no point in caching the whole text view - why not just cache the text?
Try something like that:
Have your FormCollectionViewCell just have the text view as subview, and modify your code like so:
class YourViewController : UIViewController, UICollectionViewDataSource, UICollectionViewDelegate
{
var texts = [IndexPath : String]()
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FormCell", for: indexPath)
if let formCell = cell as? FormCollectionViewCell {
cell.textView.text = texts[indexPath]
return cell
}
}
func collectionView(_ collectionView: UICollectionView,
didEndDisplaying cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
{
if let formCell = cell as? FormCollectionViewCell {
{
texts[indexPath] = formCell.textView.text
}
}
}

Changing the text of a label when a collection view cell is tapped in swift 4.0

I am having a problem with changing the text of a label when a collection view cell is tapped. I have tried using didSelectItemAt and didHighlightItemAt but nothing worked. Here's what my cell looks like:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.subjectName.text = "Selected"
}
You need
let cell = collectionview.cellForItem(at: indexPath) as! CollectionViewCell
cell.subjectName.text = "Selected"
but note because of cell dequeuing this change is temporary when the cell is still shown if you scroll around you may find another text inside that index , so reflect the changes in the array model of the collection and reload that indexPath
var statesArr = ["Selected","Default",,,,,,,,,,]
inside didSelectItemAt
statesArr[indexPath.row] = "Selected"
self.collectionView.reloadItems(at:[indexPath])
inside cellForItemAt
let cell = ///
cell.subjectName.text = statesArr[indexPath.row]

Perform segue when cell with ID is selected

I am trying to create a segue happen when a cell has been selected. I have tired using cell.dequeueReusableCellWithIdentifier(""). However it is returning "nil" whilst unwrapping. I have set up the cells ID correctly and they match. Any help is greatly appreciated!!
if menuTableView.dequeueReusableCellWithIdentifier("") == "logout" {
print("logout")
performSegueWithIdentifier("logoutSegue", sender: self)
}
Thanks in advance
There is a UITableView delegate method for when a user selects a cell, this is good for knowing when a user has selected a cell, but we need to identify if it is the logout cell that has been pressed.
To identify the cell we'll be setting the tag property of the your logout cell.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
//this is the indexPath row where we want our login cell to be showed
let loginCell = tableView.dequeueReusableCellWithIdentifier("login", forIndexPath: indexPath) as! LoginTableViewCell
//set the tag so when we select the cell we can see if the cell we have selected has a tag of 5
loginCell.tag = 5
return loginCell
}else {
//here goes our other cells, in this case they'll just be normal UITableViewCell
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
return cell
}
}
In our cellForRowAtIndexPath delegate method we'll instantiate the loginTableViewCell in the first row and set its tag to 5, if the row isn't 0 we simply return our normal cell
So now we have a cell where the tag is 5 and all the other cells do not have a default tag property of 0, now, when the user selects a cell we can check for this method in the didSelectRowAtIndexPath delegate method of our table view.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
if cell.tag == 5 {
//cell is login cell
//perform segue here
}
}
This delegate method gives us the table view and the indexPath of the selected cell. Now we call CellForRowAtIndexPath on the table view to get the cell that was selected. Now that we have the cell we can compare the cell's tag. If the tag is 5 the logout cell was selected so we can perform our segue there.

Resources