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.)
Related
I have a collectionView within a tableview and I am trying to access the collection view's indexPath in order to get its contents upon selecting it.
Here is my code:
TableView
//This grabs the indexPath of the collectionView Selected with its contents
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedDeal = dealArray[indexPath.row]
}
ViewController
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let cell = tableView.dequeueReusableCell(withIdentifier: "SpecialPicTableViewCell", for: indexPath) as! SpecialPicTableViewCell
//Defines the selected
let deal = cell.selectedDeal
//Grabs selected information here
dealSelected = deal.title ?? ""
//Then segues
performSegue(withIdentifier: "goToDeal", sender: nil)
tableView.deselectRow(at: indexPath, animated: true)
}
However, the selection does not register with the TableView. Nothing happens as a result.
The only information in the collection view is information you put there. You shouldn't need to get any information out of the collection view cell because you already have it.
The code you posted dequeues (possibly creates) a cell, cleans it up and gets it ready for use (possibly reuse.) That cell that you essentially created will know nothing about the data that is in the cell that was selected.
What you should be doing instead, is referring to the array where you store the deals to display and figuring out what deal is at the selected index path.
I have a UICollectionView that appropriately recognizes taps on its cells in its collectionView(didSelectItemAt: ) delegate method.
However, I then embedded a collection view within the cell itself (so that each cell has its own collection view inside of it), but now the parent cell is not recognizing any taps anymore, I'm assuming because the embedded collection view is eating them up.
Is there some property that needs to be set so that the original (parent) cells register taps again even with their embedded collection views?
This functionality can be confusing, as users are accustomed to "something else" happening when interacting with a control in a table view cell, rather than it selecting (or also selecting) the row itself.
However, if you really want to do that...
One approach is to use a closure in your cell. When you handle didSelectItemAt use the closure to tell the table view controller to select the row.
Note that Apple's docs point out:
Note
Selecting a row programmatically doesn't call the delegate methods tableView(_:willSelectRowAt:) or tableView(_:didSelectRowAt:), nor does it send selectionDidChangeNotification notifications to observers.
So, if you need to execute code when a table view row is selected, you'll need to call that yourself - something like this:
func myTableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("My didSelectRowAt:", indexPath)
}
Using the code in my answer to your previous question...
In SomeTableCell add this closure setup:
public var didSelectClosure: ((UITableViewCell) ->())?
and, still in SomeTableCell:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("collectionView didSelecteItemAt:", indexPath)
print("Calling closure...")
didSelectClosure?(self)
}
Next, in cellForRowAt in the table view controller, set the closure:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "someTableCell", for: indexPath) as! SomeTableCell
cell.rowTitleLabel.text = "Row \(indexPath.row)"
cell.thisData = myData[indexPath.row]
cell.didSelectClosure = { [weak self] c in
guard let self = self,
let idx = tableView.indexPath(for: c)
else { return }
// select the row
tableView.selectRow(at: idx, animated: true, scrollPosition: .none)
// that does not call didSelectRowAt, so call our own func
// if we want something to happen on row selection
self.myTableView(tableView, didSelectRowAt: idx)
}
return cell
}
you can implement UICollectionViewDataSource & UICollectionViewDelegate methods of inner collectionViews inside the cells itself & pass the events with closure to main class with main UICollectionView.
I have a custom uitableviewcell called customContactsTableViewCell. I am trying to use this code to update the uitableviewCell content
let cell1 = self.contactsTableView.dequeueReusableCell(withIdentifier: "contacts", for: IndexPath.init(row: index, section: 0)) as! customContactsTableViewCell
cell1.cellView.image = statusFinal.1
self.contactsTableView.reloadData()
But nothing happens. no errors and the cellview is not updated
Try:
let cell1 = tableView.cellForRow(at: IndexPath(row: index, section: 0)) as! customContactsTableViewCell
cell1.cellView.image = statusFinal.1
If you are created your tableview through Storyboard just go to your tableview and set cell custom class to customContactsTableViewCell And you'r cell identifier should be contacts if everything is already setup like this, then just check your these lines.
cell1.cellView.image = statusFinal.1
self.contactsTableView.reloadData()
After reloadData it redraw tableview again and setup data in it through this tableview Delegate function.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
/// your code
}
It will again update your data with old data, Just save this data statusFinal.1 in your data model and then whenever you require reload, it will get update through updated data. Hope so you got the point, if need more info let me know.
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.
I have successfully implemented a search function. I want to be able to search my list, select an item on the list, then return to the main tableview while the item remains selected. How do I do this?
This is the tableview without any selections or character typed into the searchbar. Items do not have a detail view. Items do have more information that can be retrieved, e.g. url. This data must be retrieved later when a user presses the "mail" button top left.
This is the list with search results. The grey highlight of the cell indicates that the cell is selected. How do I now return to the main tableview, whilst keeping the selection? I only see the cancel-button top right, the cross-button in the searchbar top middle, and the "search" button on the lower right part of the keyboard. None bring you back to the main tableview whilst storing the selection.
Based on the suggested answers, I was able to store the row's index path, using the function below:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let rowToSelect = indexPath
println(rowToSelect)
selectedCellTitle = selectedCell?.textLabel?.text ?? ""
println("The stored cell is called \(selectedCellTitle)")
}
However, I haven't succeeded in reselecting the row in the main tableview. My code is below. It looks like the constant "rowToSelect" is not carried over to another function (see the one before last line of code). How do I fix this?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
if tableView == self.searchDisplayController!.searchResultsTableView {
cell.textLabel?.text = filteredPublications[indexPath.row].valueForKey("fullTitle") as? String
cell.detailTextLabel?.text = filteredPublications[indexPath.row].valueForKey("journal") as? String
} else {
cell.textLabel?.text = publications[indexPath.row].valueForKey("fullTitle") as? String
cell.detailTextLabel?.text = publications[indexPath.row].valueForKey("journal") as? String
self.tableView.selectRowAtIndexPath(rowToSelect, animated: true, scrollPosition: .Top)
}
return cell
}
The UITableView Delegate has a function tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath. This function get’s called when a row is selected.
If you listen for this function and save the selected indexPath, you can use selectRowAtIndexPath to (re)select it in your main view.
Implement this function to listen for any selections made in your tableView
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//get back to your filled UITableView
//Save "indexPath" to a variable
}
When you get back to the view controller where you have your UITableView
self.tlbView.selectRowAtIndexPath(“above declared variable”, animated: true, scrollPosition: .Top)
If you're able to hold the index of the Cell in your tableViewController, you could use self.tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: .Top) as soon as you come back to your tableView. This will keep the cell grey like in your picture even if you scroll the table.