Basic Transitioning Between View Controllers in Swift - swift

I am working to implement code that will move between 2 View Controllers, vc1 and vc2.
vc1 toggles between 2 SKViews, and vc2 just displays some simple things in UIView.
vc1 and vc2 are both in the main storyboard.
vc1 is displayed first, then at a point in an SKScene it segues to vc2:
let currentViewController: UIViewController = (self.view?.window?.rootViewController)!
currentViewController.performSegue(withIdentifier: "VC1toVC2", sender: currentViewController)
After a short delay in vc2, it segues back to vc1:
self.performSegue(withIdentifier: "VC2toVC1", sender: self)
These segues work.
Then, with the same code as above (using the same segue):
let currentViewController: UIViewController = (self.view?.window?.rootViewController)!
currentViewController.performSegue(withIdentifier: "VC1toVC2", sender: currentViewController)
...the 2nd time it does not work to segue from vc1 to vc2. There's no exception, just that vc2 does not display.
I found some other posts that don't address the issue, for example:
* Swift segue not working? - suggests that UI updating has to be in main thread, and this one that shows how to do that in Swift 3 - How do I dispatch_sync, dispatch_async, dispatch_after, etc in Swift 3? --- I get the same result
* other posts suggested using the view controller's .show or .present method --- I get the same result
Any thoughts/guidance?

I solved this using a delegate as other posts have mentioned, eg How to call method from ViewController in GameScene. The delegate allows the SKScene object to call / run code in its View Controller. This allows the View Controller to perform the segue instead of trying to perform the segue from the SKScene object (not sure that's even possible, to perform the segue from the SKScene object). Here's a simple tutorial as well https://useyourloaf.com/blog/quick-guide-to-swift-delegates/ if you're not all that familiar with delegates in Swift. I can post more details if it would help anyone.

Related

Dismiss View when Label is pressed in TableViewCell

i have a label with a TapGesture in a table view cell that should dismiss the view controller, which is embedded in a Navigation controller, if its pressed.
Normally I would do it like that:
_ = navigationController?.popViewController(animated: true)
OR
self.dismiss(animated: true)
But this is not working inside the table view cell class.
Would be great if someone could help me!
For it to work you should pass that obligation to the viewController which is managing your tableView. So above your tableViewCell class declare a protocol with a function which the delegate (In this case the ViewController which is holding your tableView) should implement when called. So when a delegate gets called inside a ViewController dismiss the ViewController if it was modally presented and pop it if it was pushed, so one of the methods you mentioned above should work. If you Don't know how custom protocols work, try reading this article https://medium.com/#aapierce0/swift-using-protocols-to-add-custom-behavior-to-a-uitableviewcell-2c1f09610aa1

swipe action not popping to previous controller - what am I missing?

I have the UI Gesture Recognizer connected to my swift file and have this code to pop over to the previous controller. However, I'm not having luck atm. what am I missing?
#IBAction func leftswipe(_ sender: Any) {
[navigationController?.popViewController(animated: true)]
}
You need embed first viewController into UINavigationController and use "Show" or "Show Detail" segue`s type.
Try printing navigationController, it must be nil. Are you sure you have the first viewController is embedded in navigation controller and this viewcontroller is in the navigation controller stack? Also I noticed [] brackets in your code. Don't use that in Swift.

Going from one ViewController to another ViewController and updating

Now I have 3 Viewcontrollers like this:
HomeViewcontroller = VC1.
Viewcontroller2 = VC2 --> has a table view with cells on it.
Viewcontroller3 = VC3 --> allows me to edit each cell in VC2 or delete.
I am using NavigationController between Viewcontrollers, to get the nice "back" button :)
Lets say I am in VC1, I press a button and I go to VC2.
Now let's say I have 4 cells.
I press cell number 2 and I go to VC3, where I can edit what I have in the cell or even delete it.
This is all working OK (tested it).
Let's say I want to delete that cell. I have a button on VC3 and I use it and I delete the cell (this is tested and also working OK).
Now when I do this I push into VC2 again like this:
let viewController = storyboard?.instantiateViewController(withIdentifier: "VC2") as! VC2!
self.navigationController?.pushViewController(viewController!, animated: true)
The problem is that when I do this I go to VC2 but I doesn't reload my tableView has it should (the erased cell it's still there).
To fix this I tried adding some code in VC2, in the viewDidLoad:
self.tableView.reloadData()
Now I managed to do it work. I mean when I delete a cell from VC3 I am pushed to VC2 and the cell it's not there. However If I press the back button it takes me to the previous cell that I have deleted,,, If the cell doesn't exist anymore I don''t want it to take me there. I want it to take me to the VC1 (home).
How can I make this work?
Perhaps I should do this in another way and not pushing like that?
Practically you are doing wrong, when delete / edit operation done on VC3 and you want to come back on VC2, why you are doing pushViewController , you have to apply popViewController.
You have to apply self.navigationController?.popViewController(animated: true) coming back to VC2 from VC3.
Remove
let viewController = storyboard?.instantiateViewController(withIdentifier: "VC2") as! VC2!
self.navigationController?.pushViewController(viewController!, animated: true)
Add
self.navigationController?.popViewController(animated: true)
Also
Put reloadData() method for table view in viewWillAppear. like below code.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tableView.reloadData()
}
Some reference material to read about UINavigationController.
https://developer.apple.com/reference/uikit/uinavigationcontroller
Put your self.tableView.reloadData() in an override of viewWillAppear. viewDidLoad is only called once after the viewController is created, but viewWillAppear is called every time it is about to appear on screen.
Also, don't use a push to return to VC2 from VC3. You should pop the current viewController.
self.navigationController?.popViewController(animated: true)
Instead of pushing ViewController2 again from ViewController3 you need to create one delegate and implement that delegate within your ViewController2 now create the instance of that delegate in viewController3 and set that delegate when you are moving from ViewController2 to ViewController3. Now when you update or delete data of ViewController2's simply use that delegate to call method from ViewController2 and inside that method update your DataSource and reload the tableView.
N.,
Normally when different view controllers are embedded in a navigation controller, you push up the stack and going back you pop from the stack. As I understand your story it seems you are only pushing new view controllers on the stack.
Your first view controller is VC1. You press a button and you push the new view controller on the stack, VC2. The navigation controller now has 2 view controllers on the stack VC1 and VC2. Then you select a cell in the table view of VC2 and it takes you to VC3.
The navigation controller now holds 3 view controllers on the stack VC1, VC2 and VC3. Now you delete a cell and you push a NEW view controller on the stack, which is again a VC2, but a new one. The navigation controller now has four view controllers on the stack VC1, VC2, VC3 and another instance of VC2.
I would assume you have a model object containing the data in the table views of VC2 and VC3. When you delete the cell you should delete the cell in your model object and not push a new VC2 on the stack, but dismiss the VC3. You can do this with an #IBAction in VC2 to which your delete button is connected. It then pops the VC3 from the stack and you land back in VC2 in the IBAction method to which you have linked the delete button. The navigation controller then only has 2 view controllers on the stack again, VC1 and VC2.
In that IBAction method you can reload the table view and the table view will call the datasource method
tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
This method will look at your model object and not find the deleted data and your cell is gone.
When you then push the back button, the navigation controller pushes the VC2 from the stack and is left with only one view controller VC1. You will land back on your page.
Hope this helps.

Simulating a UINavigationController Pop

I'm using a navigation controller and it works great. However, some custom buttons segue to new ViewControllers (not in the stack) and I don't like the push-type transition animation it uses.
GOAL:
I want to the transitions (described above) to mimic a nav-stack pop transition (where the current view slides to right, revealing the view underneath).
I've successfully simulated a nav-pop using the below code, but then after my buttons and nav-bar back button are super glitchy.
#IBAction func Page2_to_Page1_ButPush(sender: AnyObject) {
var curPage: UIViewController = self.storyboard!.instantiateViewControllerWithIdentifier("Page2_ID") as! UIViewController
var prevPage: UIViewController = self.storyboard!.instantiateViewControllerWithIdentifier("Page1_ID") as! UIViewController
self.navigationController?.pushViewController(prevPage, animated: false)
self.navigationController?.pushViewController(curPage, animated: false)
self.navigationController?.popViewControllerAnimated(true)
}
Can anyone supply a simple way to make my segues simulate a nav-pop?
Details:
X-Code 6.4
Swift
Storyboard
Here's the solution:
iOS Segue - Left to Right -
Simple, easy, works perfect. Add the "SegueFromLeft" class code shown in the link, select the segue you want to present from left, select custom, pointed it at "SegueFromLeft", and the animation is dead on.
Bonus: Even better, it syncs up with the navigation controller, so the nav "Back" button still works.

UIViewControllers tripping over each other

Need some best practice advice here...
Navigation based application. Root view is a UITableView where user can drill down to a detail UIViewController (call it VC1). User wants to initiate some task but it may require additional info before it can proceed. If so then VC1 allocs & presents modal VC2 using the "flip" transition holding a strong reference to VC2 in a property.
All fairly standard. Here's where I'm running into trouble. Once user fills out required info in VC2 the app can either continue on to a MFMailComposeViewController or flip back to VC1. If they continue to MailCompose then when that dismisses it should return to VC1.
VC2 has a weak reference to VC1 and the problem arrises when VC2 tries to dismiss itself and present MFMailComposeViewController:
[self dismissModalViewControllerAnimated:YES];
[VC1 performSelector:#selector(showMailModalView) withObject:nil afterDelay:0.2];
I get a EXC_BAD_ACCESS on VC1 because, apparently, my weak reference to VC1 has already been dealloc'd even though VC1 has strong reference to VC2!?!
So my question is...how should this be handled? Would the delegate pattern be better? How would that be triggered?
Note: VC1 is quite large and VC2 isn't often needed so I'm trying to keep VC2 as separate as possible from VC1 (including its own NIB).
VC2 has a weak reference to VC1 and the problem arrises when VC2 tries
to dismiss itself and present
MFMailComposeViewController:
What you have is a circular dependency, since VC1 knows about VC2 and then you let VC2 know about VC1. And When you have circular dependencies, you get all sorts of problems.
You should be using the delegate pattern here. When VC1 presents VC2, it should make itself VC2's delegate. When VC2 is done and wants to dismiss itself, it should let the delegate take care of that operation. In other words, the thing that shows VC2 should be the thing that dismisses VC2. VC2 should be implemented in such a way that it shouldn't know what presented it, only that what presented it will be in charge of dismissing it.
Two similar answers I've given recently:
Pop-up modal with UITableView on iPhone
call method in a subclass of UIView
I've run into the same problem and I'm trying to recall how I fixed it.
You might try calling:
[self.parentViewController dismissModalViewControllerAnimated:YES]
Or could you have your showMailModalView method handle dismissing the current modal view controller before showing the mail composer?