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.
Related
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.
Back Button shows in StoryBoard but not Simulator.
I added a segue from TableViewController to DetailViewController to pass data. The Storyboard automatically shows a Back Button on DetailViewController, but when I run the Simulator, the button doesn't show up.
This is my Storyboard:
A closer look of TableViewController and DetailViewController:
But in my Simulator the button doesn't show up:
The hierarchy of the whole project:
I want to know where to configure the back button(in my segue?), or instead deleting the button(not letting it show in the Storyboard) so I can create one myself.
The code of my segue in my TableViewController:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetailView", sender: indexPath)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch (segue.destination, sender) {
case (let controller as DetailViewController, let indexPath as NSIndexPath):
controller.receivedName = storeList[indexPath.row].name!
controller.receivedDesc = storeList[indexPath.row].desc!
controller.receivedRate = storeList[indexPath.row].rate
controller.receivedUrl = storeList[indexPath.row].url!
default:
print("unknown segue")
break
}
}
Your initial view controller in the storyboard is actually the TabelViewController (you can see there is an arrow to it).
Thats' why when you start the scene and the TableViewController shows but it is not embedded in the navigation because the navigation controller has never been created.
Just change which is the initial view controller to be the navigation controller or any other before the navigation controller which holds the TableViewController.
You can just drag the arrow to change the initial controller in the storyboard
I've just recently started learning about Xcode and the swift language. To get myself familiarise with them I'm trying to build a simple app with what I've learnt so far. 1
The screenshot shows what I want to achieve. VC1 and VC2 are both a navigation + tab VC. When the user taps on the "Preset" button on the navigation bar of VC1, the screen will switch to VC2 which has a table with different preset values. What I did was to create an action segue (show) from the "preset" button to VC2. All good so far.
This is what I want to do but couldn't figure a way to do it: At the table at VC2, when the user click on any row, he will be brought back to VC1 and the value in the selected row is being inserted into a textfield in VC1.
What I had tried to do was to create an action segue (show) from the table cell to VC1. But by doing so, the navigation bar and tab bar on VC1 and VC2 has disappeared 2
Is what I'm trying to do achievable? if yes, what did i do wrongly?
I tend to use storyboards with an IBAction to return to the previous view controller, such as
#IBAction func userDidSelectTableCell() {
self.navigationController?.popViewController(animated: true)
}
But in your case I think your after a unwind segue, I've not used one but you can read more about them at this following link https://spin.atomicobject.com/2014/10/25/ios-unwind-segues/.
Hope that helps
For your purpose you need to use a unwind segue. In order to achieve this, you firstly need to declare a global variable in your VC2 class which stores the value of the selected row. You can use the didSelectRow method for that.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCell = indexPath.row
self.performSegue(withIdentifier: "YourIdentifier", sender: self)
}
you need to add a #IBAction method in you VC1 class.
#IBAction func unwindtoVC1(_ sender: UIStoryboardSegue){
if let sourceVC = sender.source as? YourTableViewController {
yourTextFiled.text = sourceVC.selectedCell
}
}
In you storyboard, in VC2, Control+drag-drop from viewController to Exit portion to create a segue.
From the pop-up that comes, select the unwindtoVC1 method.
Now, select the segue and in the attributes inspector, give it any identifier.
Use this identifier in your VC2 class, in the didSelectRow method, in the self.performSegue(withIdentifier: "YourIdentifier", sender: self)
Hope this helps.
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.
I have a Tab Bar application, and one of the tabs, which contains a Table View, segues into a third view when a table cell is pressed. The view controller acts as a delegate for the UITableView, and I trigger the segue programatically as follows:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("cell tapped, starting segue")
performSegueWithIdentifier("showDetails", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
println("prep for segue")
// TODO - more code here
}
Finally, I set up the following code to debug the problem with the third view:
class DetailsViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
println("did load")
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
println("will appear")
}
}
The problem is that when I press a table cell for the first time, the viewWillAppear function never gets called until I interact with the UI in some way (e.g. just a tap anywhere on the screen). The view that I want to segue into doesn't show up, as if the screen didn't get refreshed. However, when I tap the screen, the whole animation runs and I can segue as intended. This is my output when I tap a cell:
cell tapped, starting segue
prep for segue
did load
I tried to find solutions online, but all the issues I found it seems to just not work at all. In my case, it is working, but not immediately.
In case it helps, here's a screenshot of my storyboard:
Sefu found the answer and posted it in the comments, I ran into the same issue and his solution worked for me. The trick is to make it so the cell that is selected that triggers the segue needs to have a selection style set (not None), and I also found that deselecting the cell in tableView:didSelectRowAtIndexPath: also needed to happen.
Ran into a similar problem while having my selectionStyle = .None .
An option you can use, if you're like me and don't want a selectionStyle applied is to set the cell item back to unselected in the prep for segue.
That seemed to stopped the 'issue' I was seeing where the segue would work perfectly once, but all subsequent calls would require selecting the cell twice.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//sending the index path up as the sender so the prep for segue can access the cell
self.performSegueWithIdentifier("segueID", sender: indexPath);
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "segueID"){
if let indexPath : NSIndexPath = sender as? NSIndexPath{
tableViewReference.cellForRowAtIndexPath(indexPath)?.selected = false;
let destinationVC : UIViewControllerClass = segue.destinationViewController as! UIViewControllerClass;
destinationVC.customMethod(/* some value */);
}
}
}