I have a tableview list with clickable cells, when one is clicked a new viewcontroller opens up. When a back button is clicked and the first VC is called, the tableview resets to the top of the list. How can I change this so when the back button is clicked the tableview goes back to the original cell clicked? From what I understand, I need a tableview.scrollToRow, but I'm getting a little lost in the indexPath that I need to select (believe I need to save the last selected row, but now sure how to do this)
Here's the code:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let webVC = UIStoryboard.init(name: "MainVC", bundle: nil).instantiateViewController(withIdentifier: "SecondaryVC") as! WebViewController
webVC.urlLink = self.listings?[indexPath.row].url
self.present(webVC, animated: true, completion: nil) }
override func viewWillAppear(_ animated: Bool) {
tableview.scrollToRow(at: indexPathSelected, at: .middle, animated: false)
}
Your problem is that you are actually moving forward to a new instance of your view controller rather than back to the existing instance. If you move back then the state of the view controller will be as you left it and there will be no need to do anything to the tableview.
You should use an unwind segue to move back
As your cell is respond to your touch(and you did not call deselectRow(at indexPath: IndexPath) after selecting cell), the tableView will keep your selection(s), so when you back to your viewController just call tableView.indexPathForSelectedRow(or tableView.indexPathsForSelectedRows) in your viewWillAppear() method.
Related
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.
Swift 5, Xcode 10
My app uses two ViewControllers:
VC1, UITableView, clicking on a cell calls
VC2, displays further information about a cell with the default UINavigationBarItem "back" button on the top left and an addition "save" button at the bottom
The "save" buttons saves the changes made. To go back to the VC2, you can either click on the "back" button or save the changes, which also automatically loads VC2 using:
#IBAction func onClickSave(_ sender: Any) {
//Save changes
delegate?.passRowSavedBack(true) //Tell VC1 that changes were saved
navigationController?.popViewController(animated: true)
}
If I go back using the UINavigationBarItem "back" button, the cell also keeps its selection color for a second, then removes it (like in the iOS "Contacts" app) using this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
myTableView.deselectRow(at: mylastRowPickedIndex!, animated: true)
}
Example gif.
The problem is: If I go back with the "Save" button, this code is called too but there's no animation.
My guess is that popViewController takes longer to go back than whatever the "back" button calls, so the animation is played but you don't "arrive" in time to see it.
But how do I fix this? Is there a different way to go back to VC1 through the "Save" button (without removing the default "back" button!) that still plays the animation?
Edit: What I'm doing exactly:
In VC2: Save the changes
Tell VC1 that they were saved with a delegate (check 1st code above):
protocol PassingProtocol {
func passRowSavedBack(_ valueSent: Bool)
}
Go back to VC1 using popViewController (check 1st code above)
In VC1: deselectRow in viewWillAppear (check 2nd code above)
rowsSaved[lastRowPicked] = true, so it can add an accessoryType in here:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
cell.accessoryType = (rowsSaved[indexPath.row]==true) ? .checkmark : .none
return cell
}
myTableView.reloadData() - so it displays the new checkmark
Okay, finally I think I understand what is the problem.
You are calling myTableView.reloadData() and this is the problem with deselecting animation.
You can move myTableView.reloadData() in viewDidAppear() and everything will be okay.
I want to return to my previous viewController, so I used the dismiss method, but when I select a cell nothing happens. This is the code that I have right now.
override func tableView(_ tableView: UITableView, didSelectRowAtindexPath: IndexPath) {
delegate?.dataReceived(data: universityArray[indexPath.row].name)
dismiss(animated: true, completion: nil)
}
Form the top of my head, i can see two potential problems here
The tableView delegate is not set
tableView.delegate = self
The viewController was not presented rather pushed, of that is the case try popViewController instead of dismiss
self.navigationController?.popViewController(animated: true)
Hope the problem was one of the above.
I'm using a tableView like the iPhone's note application.
I can add/delete note (cells).
I'm facing a little issue when I delete a note.
Here's my code:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true)
switch (indexPath as NSIndexPath).row {
case 0:
let Liste1 = self.storyboard!.instantiateViewController(withIdentifier: "liste1") as UIViewController
self.present(Liste1, animated: false, completion: nil)
case 1:
let Liste2 = self.storyboard!.instantiateViewController(withIdentifier: "liste2") as UIViewController
self.present(Liste2, animated: false, completion: nil)
case 2:
let Liste3 = self.storyboard!.instantiateViewController(withIdentifier: "liste3") as UIViewController
self.present(Liste3, animated: false, completion: nil)
default:
break
}
As you can see, each indexPath.row can open a counted ViewController.
If I have 3 notes and I delete the 2nd, the 3rd will open the 2nd viewController. Tell me if you don't understand..
I know the problem , I don't know if there are some solutions to open specific viewController .
I want Cells is linked to a viewController, not by the indexPath..
Thanks in advance !
The implementation here is wrong. How it should be is that a ViewController (say named DetailsViewController) should be pushed whenever the user selects a cell. Only the text associated text/note should change. The correct note should be fetched from an array of all the notes (with index being the indexPath.row of the selected cell) and then pass is to DetailsViewController while presenting it.
Deleting a note:
When the user deletes a note, just delete the not from this array of all notes.
I haven't coded this, but I am wondering if it is even possible and how it can be done. Let's say I am on VC1. If I press a button on VC1, I want to be taken to VC2, which has a table view, and then have it automatically select the first cell and segue to a VC3. Anybody with experience of doing this.
Assuming this is more of a conceptual question, yes, this can be done. I've worked on an app where the feature required animating through multiple levels of table views programmatically using NSNotification's. Essentially, you can just have a shared method that is called from tableView:didSelectRowAtIndexPath: that you would call directly to "select" the table cell and do whatever the default action was.
Let me know if this answers your question.
Yes It can be done.There can be many ways, One way is this -
1.In Your viewDidAppear of VC2 have this lines -
override func viewDidAppear() {
super.viewDidAppear()
let rowToSelect:NSIndexPath = NSIndexPath(forRow: 0, inSection: 0)
tableView.selectRowAtIndexPath(rowToSelect, animated: true, scrollPosition: UITableViewScrollPosition.None)
self.tableView(tableView, didSelectRowAtIndexPath: rowToSelect)
}
In your tableView method didSelectRowAtIndexPath -
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0 {
self.presentVC3()
}
}
func presentVC3() {
//Initialize VC3
// And Segue to VC3
}