IOS/Swift: Save the cell selection on UITableView - ios

I have a master detail layout with table views in both of the views. Left panel has a list of patients and right has a list of documents, what happens is by changing the patient selection documents will be reloaded and if the user clicks on a document, it will segue to a webView to display PDF.
For consistency purpose i made the first cell in the Patient table to be selected by default using the below code
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
let rowToSelect:NSIndexPath = NSIndexPath(forRow: 0, inSection: 0);
self.tableView.selectRowAtIndexPath(rowToSelect, animated: true, scrollPosition: UITableViewScrollPosition.None)
self.performSegueWithIdentifier("showDetail", sender: self)
}
work's fine right?, Yes but it gets a bit inconsistent from here, after closing this PDF document (by clicking done on navigation), my Previously selected cell will go off and it again points to the first Patient. Is there any way to save this selection? Oh and even to save the selection on the details page too. Thanks.

Create an NSIndexPath property.
Change your viewDidAppear to only default-select the first cell if there wasn't something previously selected:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
if (!self.rowToSelect) {
rowToSelect = NSIndexPath(forRow: 0, inSection: 0);
self.tableView.selectRowAtIndexPath(rowToSelect, animated: true, scrollPosition: UITableViewScrollPosition.None)
self.performSegueWithIdentifier("showDetail", sender: self)
}
}
In the didSelectRow delegate method store the selected index path in self.rowToSelect.

Related

Cell stays highlighted and interactively unhighlights as we swipe

After we tap on the table view cells to push and pop to the detail view, if we swipe back to the previous table view, you'll notice that the cell stays highlighted and interactively unhighlights as we swipe.
How can this be programmatically implemented in UIKit?
The following reference illustrates the behaviour:
WWDC20 Introduction to SwiftUI: https://developer.apple.com/videos/play/wwdc2020-10119/?time=630
First, if you haven't already, you need to mark your "selected" when you tap on it:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? SubclassedCell else {
return
}
//`setSelected(:animated:) is built into `UITableViewCell`
cell.setSelected(true, animated: true)
...
Then in viewWillAppear(_:) you're going to coordinate the deselection animation with the edge swipe animation:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//1
guard let selectedIndexPath = tableView.indexPathForSelectedRow else {
return
}
//2
if let transitionCoordinator = self.transitionCoordinator {
transitionCoordinator.animate(alongsideTransition: { (context) in
self.tableView.deselectRow(at: selectedIndexPath, animated: true)
}, completion: nil)
//3
transitionCoordinator.notifyWhenInteractionChanges { (context) in
if context.isCancelled {
self.tableView.selectRow(at: selectedIndexPath, animated: true, scrollPosition: .none)
}
}
} else {
//4
tableView.deselectRow(at: selectedIndexPath, animated: animated)
}
}
That's a LOT of code, here are the highlights:
Only run this if there's a selected index path. There's no selection if you’re on this screen for the first time. (Btw, the table view keeps track of its own selected index path(s). You just need to mark cells selected or not selected).
Coordinate the row deselection animation with the current animation "context" (i.e. the edge swipe animation context).
You might change your mind mid-swipe! If this happens, you want to re-select the thing you were deselecting.
Back in the day, before transition coordinators, you only had to add this one line. This else case is there in case there's no transition coordinator (old version of iOS, going back in the stack without animation, etc).
Ok..before you give up on UIKit, know there's a shortcut.
Shortcut: Use UITableViewController instead of UIViewController
Instead of subclassing UIViewController and adding a table view, just subclass UITableViewController. You still have to mark your cell selected, but that's it.
This works because UITableViewController has a property called clearsSelectionOnViewWillAppear, which is set to true by default. It takes care of everything for you.

How to fix: func selectRow in the TableView does not work when I click Cancel in search bar

I have a TableView with a playlist of music tracks. When I click on the row, the track starts to play and the current row is selected. Also, I have a search bar, which works fine. But if I click on the row, then on the search bar and then I finally on the Cancel button - the row with the current track is not selected. In other situations, like forward or backward playback, the selection of rows works well.
Simplified, my code looks like this:
class TableView: UIViewController, UISearchResaultUpdating, UISearchBarDelegate {
...
private var currentSelectedRowIndex = 0
func ...didSelectRowAt indexPath... {
...
currentSelectedRowIndex = indexPath.row
print(currentSelectedRowIndex)
}
...
func searchCancelButtonClicked... {
print("Cancel button pressed")
print(currentSelectedRowIndex)
tableView.selectRow(at: IndexPath(row: currentSelectedRowIndex, section 0), animated: true, scrollPosition: .middle)
}
}
//For example, I pressed second row
Console message:
1
Cancel button pressed
1
==========================
But the row is not selected, what's the problem?
If you look at the UITableView documentation, it clearly says that calling selectRow does not cause the delegate to receive tableView(_:didSelectRowAt:).
You can move whatever you are doing in tableView(_:didSelectRowAt:) to another function and call that function in tableView(_:didSelectRowAt:) and searchCancelButtonClicked.
Or you can use the following code to manually invoke tableView(_:didSelectRowAt:) in searchCancelButtonClicked.
func searchCancelButtonClicked() {
print("Cancel button pressed")
print(currentSelectedRowIndex)
let indexPath = IndexPath(row: currentSelectedRowIndex, section: 0)
yourTableViewOutlet.delegate?.tableView?(yourTableViewOutlet, didSelectRowAt: indexPath)
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle)
}

UITableView: swiping RowAction cancels the selected rows

I can see this behavior in both deprecated UITableViewRowAction class and UISwipeActionsConfiguration class:
If you have allowsMultipleSelection property set to true and, let's say, you have 3 rows selected:
When you start swiping any row in the table for a RowAction the previously selected rows -- all 3 of them -- become unhighlighted, and the property indexPathsForSelectedRows drops to nil.
Does this behavior make sense?
Is there any 'deselecting' callback (because I'm displaying the number of selected rows)
What are possible workarounds to persist the array of selected rows?
UITableView enters editing mode when you swipe a row in the table. This is your
'deselecting' callback
You can backup your selected rows on entering the mode and restore on exiting:
class ViewController: UITableViewController {
var indexPathsForSelectedRows: [IndexPath]?
override func setEditing(_ editing: Bool, animated: Bool) {
if editing {
indexPathsForSelectedRows = tableView.indexPathsForSelectedRows
} else {
indexPathsForSelectedRows?.forEach { tableView.selectRow(at: $0, animated: false, scrollPosition: .none) }
}
super.setEditing(editing, animated: animated)
}
}
Also note that if you re-arrange/delete/insert rows during editing, you'll need to update your stored indexPathsForSelectedRows accordingly so you restore correct index paths.

Select First Row in UITableViewController on load

I saw multiple threads on selecting the first row by default in a UITableViewController with a NavigationController, and I used some of the code I found via some of the communities answers, but it's still not working for me.
override func viewDidAppear(_ animated: Bool) {
let indexPath = IndexPath(row: 0, section: 0)
tableView.selectRow(at: indexPath as IndexPath, animated: false, scrollPosition: .middle)
}
It just highlights any row I put as the indexPath, but it doesn't actually go to the view associated with that indexPath. I feel like it's connected right though, because when I do actually click the row it goes to the view. Am I missing a step here?
Thanks in advance!
Calling this method does not cause the delegate to receive a
tableView(:willSelectRowAt:) or tableView(:didSelectRowAt:) message,
nor does it send UITableViewSelectionDidChange notifications to
observers.
You must call UITableview's delegate didSelectRowAt method.

How to select UITableView cell programmatically (Swift 2)

I have an UITableViewController and I need to select and scroll to one of the cells when the view loads. The reason I need the cell selected rather than just making it look selected (with an accessory or whatever) is that I want it to have a different background and no separators above/below (which is what happens when a table cell is selected), and I want the cell initially visible (if it's too far down).
I've read the two other answers and they don't work or I don't understand them. I'm not sure where I should put the self.tableView.selectRowAtIndexPath and self.scrollToRowAtIndexPath. I tried putting it in viewDidLoad() and it had no effect.
Try to use viewDidAppear
func viewDidAppear(animated: Bool){
super.viewDidAppear(animated)
let path = NSIndexPath(forRow: 1, inSection: 0)
tableView.selectRowAtIndexPath(myPath, animated: false, scrollPosition: UITableViewScrollPosition.None)
}
Swift 4:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let path = IndexPath(row: 0, section: 0)
tableView.selectRow(at: path, animated: false, scrollPosition: .none)
}
Use the selectRowAtIndexPath method of UITableView in your view controller's viewDidAppear method
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// replace forRow: value with the index of the cell
// replace inSection: value with the section the cell is in
let indexPath = NSIndexPath(forRow: 2, inSection: 0)
tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: .Middle)
}
Given solutions didn't work for me. So, here is the solution for swift 5
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
//Choose the row and section
let indexPath = IndexPath(row: 0, section: 0)
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle)
tableView.delegate?.tableView!(tableView, didSelectRowAt: indexPath)
}

Resources