So I have a tableview that has a list of items in each cell. Each of these cells contain an image view which, upon being tapped, expands the cell and displays the image for that item. When I scroll down the table view and scroll back up to the cell that was selected, the image is gone. I know this is due to reusing cells but I'm not sure on how to keep the expanded cells image in place while scrolling through other items.
The closest I've come is here:
my table view reuse the selected cells when scroll -- in SWIFT
If someone could lend me a hand that would be awesome. Thanks!
Edit: Adding code snippets - Sorry for the wait.
fileprivate var expandedRowIndex: Int?
// cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
// CatalogItem row.
let item = self.items[indexPath.row]
let expanded = indexPath.row == self.expandedItemRowIndex
// Return standard catalog item cell.
let reuseID = expanded
? CatalogItemCell.PROTOCELL_EXPANDED.id
: CatalogItemCell.PROTOCELL.id
let cell = tableView.dequeueReusableCell(withIdentifier: reuseID) as! CatalogItemCell
// Reset thumbnail image back to nil. Needed so that images appear
// only in the cell that they belong in.
if indexPath.row == self.expandedRowIndex{
cell.uiImage_Thumbnail.image = nil
}
return cell
}
// didSelectRowAt
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
// Expand row - Get the current cell and show image
self.expandedItemRowIndex = indexPath.row
let item = self.items[indexPath.row]
let currentCell = tableView.cellForRow(at: indexPath)
// Pass both the selected cell and item to the ImageManager
ImageManager.startImageRequest(currentCell: currentCell!, item: item)
if self.expandedRowIndex == indexPath.row
{
// Selected row is already expanded.
return
}
var reloadPaths = [IndexPath]()
// Collapse previously expanded row.
if let previousRowIndex = self.expandedRowIndex
{
reloadPaths.append(IndexPath(row: previousRowIndex, section: 0))
}
// Expand the selected row.
self.expandedRowIndex = indexPath.row
let item = self.items[indexPath.row]
debugPrint(item.description)
reloadPaths.append(IndexPath(row: indexPath.row, section: 0))
tableView.reloadRows(at: reloadPaths as [IndexPath], with: .fade)
}
You can maintain a selectedIndex variable.
In your cellForRow you check whether this call is for selectedCell. If yes, then do the customisation that is required for selected cell.
Also you might want to handle heightForRow, there also check whether the call is for selected cell.
You can maintain an indexPath for selected cell. If there are multiple sections.
No need to prevent it from getting reused.
Hope that helps.
Related
So after I add this code below the comment:
DispatchQueue.main.async {
self.tableView.reloadData()
// HERE
let indexPath = IndexPath(row: self.messages.count - 1, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}
My cells mess up (generated randomly) when scrolling. Before adding this code they behave as expected.
I think it has to do with the fact that cells are reused, and I don't know how to fix this.
Any explanations as to why?
cellForRowAt method for more detail:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifier, for: indexPath) as! MessageCell
cell.messageText.text = messages[indexPath.row].body
if Auth.auth().currentUser?.email == messages[indexPath.row].sender{
cell.isIncoming = false
} else {
cell.isIncoming = true
}
return cell
}
Cells are recycled. In your table view's data source, you need to dequeue a cell and fully configure it with data from your model.
If you allow your user to interact with your table view, you need to save those changes into your model so that if the cell scrolls off-screen, you recreate it correctly when it scrolls back on-screen.
You haven't provided enough information about how you set up your cells for us to provide more help than that.
If you want more specific help, edit your question to show your data source methods (in particular your tableView(_:cellForRowAt:) method.)
I am making a form in a tableview.
Let's say I have 4 different types of cells, each being a question with different kind of answers
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if sortedFixedContentType.count != 0 {
let item = sortedFixedContentType[indexPath.row]
switch item.typeId {
case "1":
let cell = tableView.dequeueReusableCell(withIdentifier: "FirstCell", for: indexPath) as! FirstCell
return cell;
case "2":
let cell = tableView.dequeueReusableCell(withIdentifier: "SecondCell", for: indexPath) as! SecondCell
cell.customDelegate = self
return cell;
case "3":
let cell = tableView.dequeueReusableCell(withIdentifier: "ThirdCell", for: indexPath) as! ThirdCell
cell.commentsTextView.delegate = self
return cell;
case "4":
let cell = tableView.dequeueReusableCell(withIdentifier: "FourthCell", for: indexPath) as! FourthCell
return cell;
}
When the tableView is loaded I want to show only first cell, and depending on the answer different cells will be shown.
For example:
FirstCell can be answered with A, B, or C,
If I answer A SecondCell will be shown with answers X and Y.
If X is the answers ThirdCell will be shown (which has no options but a TextField), and when completed FourthCell will be shown
But if in FirstCell the answer is B or C only FourthCell will be directly shown.
At the moment I was doing it by changing the height of the rows in heightForRowAt, although I think there must be an easier way.
However I'm finding a problem:
If I get to the textField in ThirdCell and then I change my first answer, SecondCell is hidden but ThirdCell is not, as the condition to it was the second answer and it's already made, so I thought on setting the height of each row as condition to, but I don't know how to do it.
So I have two main questions:
Is it possible to access to the heightForRowAt to set it as a condition?
Should I make it this way? or maybe there's a better way to get what I need? I read about adding and deleting rows dynamically to tableviews but with the same cell type, this is why I decided to hide them by their height instead.
Thanks in advance!
I think the conventional method is to not modify the height but manipulate the data source (the number of rows in section etc.) to show/hide the appropriate cells.
You should update the data source appropriately after an event and then immediately after you can use func insertRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) and tableView.deleteRowsAt(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) to insert/delete cells in the tableView
This documentation might help: https://developer.apple.com/documentation/uikit/uitableview/1614879-insertrows
What I usually like to do is monitor a variable and when the variable is updated adjust the height of the cell. Make sure that your variable has this didSet code assigned to it so your tableview updates the height when the variable changes.
var selectedRow: Int = 999 {
didSet {
tableView.beginUpdates()
tableView.endUpdates()
}
}
And then, just like you have done I affect the height of the row inside of the heightForRow function.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == selectedRow { //assign the selected row when touched
let thisCell = tableView.cellForRow(at: indexPath)
if let thisHeight = thisCell?.bounds.height {
print("Bam we got a HEIGHT!!")
return thisHeight + 50
}
}
return 60 //return a default value in case the cell height is not available
}
I Have a UITableView which is controlled by NSFetchedResultsController. I want to add single cell to the first row and make this cell static. In other words, there will be a button which will open another View Controller.
Until now, I was ok with fetched results controller and table. Now I'm a bit confused. How should I do this?
Instead using a header might be ok too, but I don't want this header to be on top all the time. I want this cell to be just like WhatsApp iOS "Create new group" cell on chats panel.
Thank you!
var dataArray = ["A","B","C"]
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataArray.count+1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
if indexPath.row == 0
{
let cell = tableView.dequeueReusableCell(withIdentifier: "CreateNewGroupCell") as! CreateNewGroupCell
return cell
}
else
{
// Get the data from Array
let data = self.dataArray[indexPath.row-1]
// Logic to show other cells
let cell = tableView.dequeueReusableCell(withIdentifier: "OtherCell") as! OtherCell
return cell
// ....
}
}
You will need to create tableview with number of rows fetched from NSFetchedResultsController +1. Also in cellForRowIndex method you will need to add a check like indexPath.row == 0 and in there you will make the changes.
Also you will have to add action for that button within that section. You can also set different custom tableview for first row.
It can be similar to following:
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row==0){
let cell = tableView.dequeueReusableCell(withIdentifier: "CellWithButton", for: indexPath) as! CellWithButton
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "OtherCells", for: indexPath) as! OtherCells
//here add data for cells from your array
}
return cell
}
I have two tables in a tableview in one UIViewController(Not UITableViewController). Now I want to let only one of the cells adding UITableViewCellAccessoryDisclosureIndicator to let users click for viewing another tableview.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell()
if indexPath.section == 0{
cell.textLabel?.text = "\(rentTitle[indexPath.row]): \(rentArray[indexPath.row])"
}else{
cell.textLabel?.text = "\(cashflowTitle[indexPath.row]): \(cashflowArray[indexPath.row])"
}
return cell
}
There are 6 rows in the first table and 5 rows in the second one. I want to add the disclosureIndicator in the third row of the second table. So the questions are: 1. How to add the disclosureIndicator in the 3rd row of the second table? 2. How to make that row actively link to another tableView? Thanks a lot!
func tableView(tableView: UITableView,accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath){
var cell = UITableViewCell()
if indexPath.section == 1{
if indexPath.row == 2{
print(indexPath.row)
}
}
}
I tried the print first.
That arrow isn't a UINavigationItem; it's a UITableViewCellAccessoryDisclosureIndicator.
To add that UITableViewCellAccessoryDisclosureIndicator "arrow" to your cell's accessory view, add this line:
if indexPath.row == 2 { //Third row
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
}
And then to perform a specific action when that accessory view is tapped, implement
tableView:accessoryButtonTappedForRowWithIndexPath:
In this method implement your navigation by checking the indexPath again.
No article explains it clearly regarding my query, I have three cells in a static table and I want to hide second cell when users taps on first cell. Any kind of help is appreciated.
Although you cannot stop the static table from trying to show your cells, you can set their height to zero, making them effectively invisible:
Add this method to your table view controller delegate class:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = super.tableView(tableView, cellForRowAtIndexPath: indexPath)
return cell == myHiddenCell ? 0 : super.tableView(tableView, heightForRowAtIndexPath:indexPath)
}
In the didSelectCellAtIndexPath method, you can set the height to 0 to hide it :
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0 {
let indexPath = NSIndexPath(forItem: 1, inSection : 0)
let secondCell = tableview.cellForRowAtIndexPath(indexPath)
secondCell.frame.size.height = 0;
self.view.layoutSubviews()
}
}
If you want an animation, just put self.view.layoutSubviews() in an UIView animation method UIView.animateWithDuration... etc
For me, setting the height to 0 for some cells and another height for other cells wasn't an option, as all my cells have different height.
I created another cell in Storyboard, and set row height of 0 (in size inspector). Then in the code, I show the cell with height = 0 if I want to hide it, if not, I show the other cell:
if (hideCell) {
let hiddenCell = tableView.dequeueReusableCell(withIdentifier: "hiddenCell",for: indexPath) as! TheWallTableViewCell
return hiddenCell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell",for: indexPath) as! TheWallTableViewCell
return cell
}