Why do UITableView actions only work on visible cells - ios

I'm working with a UITableView which is a list of friends (I have 20 friends). I have, on top of those 20 friends cells, a unique cell in another section called "ALL FRIENDS" which allows me to select all my friends in one click.
When this "ALL FRIENDS" cell is selected, this code is called :
var index = 0
for friend in self.friendsList {
var indexPath = NSIndexPath(forRow: index, inSection: 1)
self.tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: UITableViewScrollPosition.None)
index += 1
}
Everything works fine for the visible cells. When a cell is selected the background color changes but this only works for visible cells.
Any ideas why ?

Because UITableView is dynamically displayed and invisible cells are not there.
UITableView basically dequeues a cell that's about to disappear and appends it where you're scrolling. To select all of the cells, you have to do it differently:
You'll have to set a global variable to true, let's say var allSelected = true.
Then call self.tableView.reloadData()
Which will be reflected in tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell by doing cell.selected = true.
That will ensure that all of the future displayed cells will be selected as well.

So Michal's answer is great, but I would like to give you an alternative: you can create that tableview without dequeuing and reusing cells, so the cells remain alive at all points.
You can do this by creating individual cells, and adding them to an array
var array = [UITableViewCell]
for friend in self.friendsList {
var cell = UITableViewCell()
// customize your cell however you want, or use custom nibs
array.append(cell)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) {
return array[indexPath.row]
}

Related

Using slider in custom cell of a UITableView

I have a UITableView which has prototype cells which include a slider and a textfield next to it. As the slider's value changes, the value inside the textview is also changed. It works fine except for when the size of the table increases, the new cell generated when scrolling down is already shows the value of the first cell. I do realize that this has something to do with the dequeueReusableCellWithIdentifier function as the cells are reused. How can this be circumvented?
This is the code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("infoCell") as! InfoCell
cell.dressLabel.text = inputArrayFortableView[indexPath.row]
cell.uiSlider.maximumValue = 100
cell.uiSlider.minimumValue = 0
cell.uiSlider.continuous = true
cell.uiSlider.addTarget(cell, action: #selector(cell.uiSliderSetValue), forControlEvents: .ValueChanged)
if(cell.DressTextField.text != "")
{
cell.uiSlider.setValue(Float(cell.DressTextField.text!)!, animated: true)
}
print("outside uisliderSetvalue")
cell.uiSlider.tag = indexPath.row
print(cell.uiSlider.tag)
return cell
}
I am attaching a screenshot. Keep in mind the when I scroll downwards, the new cell has the same value as that of the first one, the second cell generated has the same value as that of the second and so on.
Because cells are reused, the slider will retain the value from the previous time the cell was used. Make sure to always initialize the slider in cellForRowAtIndexPath:
if cell.DressTextField.text != ""
{
cell.uiSlider.setValue(Float(cell.DressTextField.text!)!, animated: true)
} else {
cell.uiSlider.setValue(0, animated: false)
}
You have to store value of slider to inputArrayFortableView when the value is changed. You have to reload this value each time func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell calling because table cells is reuse, it stores old value.

tableviewcell repeating uibuttons

I am having a little trouble with buttons on a tableview.
I have a tableViewCell that I customised with 3 buttons. I set the buttons to hidden in interface builder and when the table loads the buttons are hidden as expected.
I then set the hidden property of the tableview to false when didSelectRow is called and hidden.true when didDeselectRow is called. This works fine as well. The problem is the buttons that are set to visible in the didSelectRow are also visible every seven cells down. they keep repeating themselves.
Below is the code that shows the buttons
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! ContactsViewCell
print("Table selected")
cell.insertEmailButton.hidden = false
cell.insertPhoneButton.hidden = false
cell.insertAllButton.hidden = false
cell.contactTextLabel.alpha = 0.2
cell.contactDetailTextLabel.alpha = 0.2
}
And this hides them when the tableViewCell is deselected
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! ContactsViewCell
cell.insertEmailButton.hidden = true
cell.insertPhoneButton.hidden = true
cell.insertAllButton.hidden = true
cell.contactTextLabel.alpha = 1.0
cell.contactDetailTextLabel.alpha = 1.0
}
I did some research and I learnt it might be the row with the buttons.hidden set to false are being reused by the tableview. But I understand from documentation that the cell being reused is from cellForRowAtIndexPath and not the cell at didSelectRow which is where I am setting the button.hidden to false.
I also tried using the cell.isSelected property in an if else statement in the cellForRowAtIndexPath to hide and show the buttons but this does not show the buttons at all.
Thanks in advance for your help
The tableview reuses the view of the cell when the table is scrolled, to save memory. So, for example, when you set the button to visible (inside didSelectRow) and then scroll down the table, the tableview will take the cells that go out of the visible screen at the top and will reuse them at the bottom, to save the overhead of creating new cells, improving performance.
That is why, your previous properties on the cells are repeating.
To get the desired hidden button on scrolled cells, I recommend setting button.hidden to true/false in
tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
This will set the button to hidden whenever a new row is scrolled into the visible view area.
Hope this helps.
I solved the recurring buttons by hiding them when I check if the cell is deselected in cellForRowAtIndexPath. This also means any cell I select will lose its selected status and buttons will disappear when it leaves the view.
I can live with that.
if cell.selected == false{
cell.emailButton.hidden = true
cell.phoneButton.hidden = true
cell.allButton.hidden = true
}
UITableView reuses its cell to improve performance. So, you can not do the way you are trying. What we have to do is, like other tableview cell info e.g. title, description, thumb image etc we also need to save the state for buttons in the array. When you want to hide a button for the cell take object at index from the array and change the button state for the button and reload that table view cell. Still if you face problem or feel difficult to understand, please feel free to ask.

Swift: return to tableview after selecting a search result

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.

UITableView strange behaviour when using more than 1 Prototype Cell

So I have a UITableView which I plan to use 2 prototype cells inside of it. Let's call them Cell A and Cell B. Cell A has it's own layout and Cell B has it's own layout.
Here's the thing, in a typical UITableView implementation with only 1 prototype cell, after setting up all the cell and it's properties, cellForRowAtIndexPath takes care of populating all the rows based on the (x) number of items from numberOfRowsInSection.
Here is where I am having a problem. I've implemented both my prototype cells in my UITableView and when I run it, I notice cellForRowAtIndexPath is only being called twice, even though I have a value in (x) number of items which is greater than 2. Doesn't matter what I set it to, it only gets called twice. I already have the necessary if statements to pick a cell prototype based on the cell index etc...so that's not the issue. The issue is cellForRowAtIndexPath just gets called twice instead of looping thru all the items.
Why is this and how can I fix it?
This is my code for the DataSource methods:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
Scripts.log("Data Count = \(indexPath.row)")
if indexPath.row == 0{
var cell: ContactDetailImageCell = tableView.dequeueReusableCellWithIdentifier(NAME_OF_CUSTOM_IMAGE_CELL) as ContactDetailImageCell
cell.cardPhoto.image = aContact.profilePicture
cell.fullName.text = aContact.getDisplayName()
cell.workplace.text = aContact.workplace
return cell
}
else{
var cell: ContactDetailPhoneNumCell = tableView.dequeueReusableCellWithIdentifier(NAME_OF_CUSTOM_PHONE_CELL) as ContactDetailPhoneNumCell
return cell
}
return UITableViewCell()
}
What height are your cells? cellForRowAtIndexPath will only get called if the table thinks it needs to display the cell. Hence the whole reuse mechanism. So if you have 8 cells and it thinks 2 fill the screen it will not ask for any more until you scroll up/down.
What are you returning for heightForRowAtIndexPath.

Swift UITableView didSelectRowAtIndexPath bug

I have a UITableView with the subtitles hidden but set up where when someone selects a cell it shows that cell's subtitle. This works fine except that after tapping any cell to reveal its subtitle if you scroll down you will find that every 12 cells have their subtitle unhidden (as well as the one it was supposed to reveal). Here is the code I'm using in didSelectRowAtIndexPath:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
for cell in tableView.visibleCells() {
cell.detailTextLabel??.hidden = true
}
var cell = tableView.cellForRowAtIndexPath(indexPath)
cell?.detailTextLabel?.hidden = false
}
I'm sure this is related to ".visibleCells()" since every 12 cells is about the height of my visible table on my iPhone 6 Plus. When I run it on a 4s in the simulator it's about every 8 cells. But I'm not sure how else to do it besides 'visibleCells'? But it's strange because it's the whole table - all the way down, every 12 cells is showing its subtitle...
thanks for any help
UITableView reuses its cells. So the cell for row a row you clicked on (unhidden the subtitle) may be used for row another row.
The solution is to define prepareForReuse() method in the UITableViewCell subclass (or make the subclass if you do not have one) and hide the subtitle again there.
Add that dataSource's method to your controller. Should work fine.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) {
var identifier = "cellIdentifier"
var cell = tableView. dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath)
cell.detailTextLabel?.hidden = true
return cell
}

Resources