Push view controller, but go back to another one - ios

Let's say that on my regular flow I have
VC1 -> VC2 -> VC3 -> VC4
But I have a requirement that when I touch a special button on VC1 it needs to go to VC4.
No problem there. The problem is that when I tap the back button on VC4, it needs to go back to VC3 instead of VC1.
I already tried pushing From VC1 -> VC2 and VC2 -> VC3 without animation and then VC3 -> VC4 with animation, but you can see a quick glimpse of VC3 which looks awful.
Any ideas?

I think in this specific case, you can insert a view controller on the navigation controllers stack after presenting the 4th viewController
if let navigationController = navigationController {
navigationController.pushViewController(vc4, animated: true)
let vc3Index = navigationController.viewControllers.count - 1
navigationController.viewControllers.insert(vc3, atIndex: vc3Index)
}
This should place VC3 next in line when the user presses back from VC4. Untested code, btw.

You can modify the UINavCon's viewControllers to achieve the order you want.
func specialButtonInVc1() {
self.navigationController?.pushViewController(fourth, animated: true)
self.navigationController?.viewControllers = [self, second, third,fourth]
}

Related

How to 'pop' a navigation controller and all of its view controllers

I have inherited a navigation controller issue in an existing app that I'm trying to solve cleanly.
This app has multiple storyboards and multiple UINavigationControllers. At one point in the app, a series of view controllers is presented modally, using a separate storyboard and a separate nav controller. When the modal process is complete, the navigation hierarchy looks something like this:
NavController1 -> VC1 ['Present Modally' segue] -> NavController2 -> VC2 -> VC3 -> VC4
When the user completes the modal activity in VC4, dismiss() is called programmatically on VC4 and the user can then navigate back to VC1 using the back button.
However, what we really need to do is to 'pop off' all of the modally presented set of view controllers (and their nav controller) when the user finishes the modal activity. The problem is that from VC3 or VC4 I can't call popToRootViewController(). I also can't traverse down the VC stack to find VC1, since the current Nav controller doesn't manage it.
A couple solutions come to mind:
1) use the notification manager and have VC1 listen for the message to pop everything off back to itself
2) pass a reference to VC1 as a delegate all the way up the chain so that VC3 or 4 can have it pop everything off
Both of these solutions follow the general maxim that the presenting VC should be the one that dismisses, but neither are what I would consider clean.
I would welcome any thoughts or alternative solutions.
Assuming that these are the way the view controllers were laid out:
NavController1 --['Root View Controller' segue]--> VC1 --['Present Modally' segue]--
--> NavController2 --['Root View Controller' segue]--> VC2 --['Push' segue]--> VC3 --['Push' segue]--> VC4
You should be able to go back to VC1 by dismissing either VC2, VC3 or VC4.
// example for vc4
vc4.navigationController?.dismiss(animated: true, completion: nil)
However, if each of the viewControllers were presented modally, you should be able to traverse through the presentingViewController to reach VC1.
var currentVC: UIViewController? = self
var presentingVC: UIViewController? = currentVC?.presentingViewController
while presentingVC != nil && !(presentingVC is VC1) {
currentVC?.dismiss(animated: true, completion: nil)
currentVC = presentingVC
presentingVC = currentVC?.presentingViewController
}
Hope that helps.
When popping, you may kick out the viewControllers from your navigation Controller, would solve your problem
extension UINavigationController {
public func removeViewController(classes : [String]) {
var vcs = [UIViewControllers]()
for viewController in self.viewControllers {
let name = viewController.className
if !classes.contains(name) {
vcs.append(viewController)
}
}
if classes.count < vcs.count {
self.viewControllers = vcs
}
}
}
now think you have 4 viewControllers , A, B, C, D, you want to remove B and C and Move Back To A
In D's View Controller
override func viewDidLoad() {
super.viewDidLoad()
//your works
let viewControllersToRemove = [String(describing: type(of:B)), String(describing: type(of:C))]
navigationController.removeViewControoler(classes : viewControllersToRemove)
}

Pass data to VC that is not the previous VC

When I go from VC1 to VC2, if VC2 gets dismissed, I could easily pass data back to VC1 by setting protocal in VC2 and have VC1 conform to it. I want to do something similar, however, with the following difference
When I go from VC1-> NavVC->VC2-> VC3. When VC3 gets dismissed, VC1 is shown. I want to be able to pass data back to VC1 and initiate some function in VC1. For example, I have an image to upload in VC3. As soon as VC3 gets dismissed, I am hoping to have a function in VC1 such as the following function where the image was the data from VC3
func uploadInitiate(image: UIImage) {}
Relationship of the three VC
VC1 is normal VC. It presents VC2 via
let navController = UINavigationController.init(rootViewController: VC2)
self.navigationController?.presentViewController(navController, animated: true, completion: nil)
VC2 is a custom FusumaCamera photo picker from cocoapods.
Once image is selected, I go to VC3 with the selected Image via
self.navigationController!.pushViewController(postVC, animated: true)
At VC3, I allow the user to interact with the image and make comments and press a button to upload to the server. I thought it would be nice to dismiss the VC straight away after button press and allow VC1 to initiate the upload with the data the came from VC3 (That way I can have a progress bar under navigation bar or display any warnings there)
You have multiple patterns that you can apply in similar situations. i will go with the delegation (recomended) example. Imagine you have VC1->
that presents modally a UINavigationController as a root controller and from there VC2 pushes VC3 to the stack. You have to define multiple protocols that will be called on the way you dismiss the VC3
protocol DismissVCDelegate : class {
func dismissViewAndStartUpload(shouldUpload: Bool)
}
Then store the property:
weak var delegate: DismissVCDelegate!
Set the delegate when to the VC3 when you push it from VC2 and conform the the protocol you defined in VC2.
Apply the same pattern all the way back to VC1 where you have passed multiple times the protocol back and you can start you upload task and you should dismiss the modally presented navigation controller like that:
func dismissViewAndStartUpload(shouldUpload: Bool) {
self.presentedViewController.dismissViewControllerAnimated(true, completion: nil)}

Remove current ViewController after instantiate and presented the next VC

In iOS project, XCode, I have two VCs, VC1 and VC2. What I want to do is in VC1, have a button to go to VC2 using
let VC2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as! VC2
self.presentViewController(loginVC, animated: true)
After VC2 appears, I will never need to go back to VC1 so I want to remove it completely as VC1 has some downloading function that may still run in background. So I want to remove VC1. I have tried the following:
let VC2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as! VC2
self.presentViewController(loginVC, animated: true) {
self.removeFromParentViewController()
}
However, that does not seem to do the job as I still see the background download in progress. Is there a simple way of doing it? I do not really want to include navigation controller in this case.

How to properly dismiss the presented ViewController and go back to the parent ViewController?

I'm facing an issue that I will describe and have found some similar questions, but I don't believe pertains to my issue.
I have a UITabBarController with 2 tabs for VC1 and VC2. VC1 segues to VC4. VC2 segues to VC3 and VC3 segues to VC4. VC4 contains an MPMoviePlayerViewController instance, as depicted below:
- - - - - -> VC1 \
TAB BAR VC / - - -> VC4
\ /
-> VC2 -> VC3 -> /
I have a Notification that listens to when the Video finishes, and dismiss VC4, and go back to whichever parent VC presented VC4, i.e. if VC1 presented VC4, then upon dismissal of VC4, should go back to VC1. Likewise, if VC3 presented VC4, then upon dismissal of VC4, should go back to VC3.
In VC4:
override func viewDidLoad()
{
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "moviePlayerPlayBackDidFinish:",
name: MPMoviePlayerPlaybackDidFinishNotification,
object: player?.moviePlayer)
}
func moviePlayerPlayBackDidFinish(notification: NSNotification)
{
NSNotificationCenter.defaultCenter().removeObserver(
self,
name: MPMoviePlayerPlaybackDidFinishNotification,
object: notification.object
)
player!.view.removeFromSuperview()
self.presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
}
I don't think I'm using the code properly because self.presentedViewController?.dismissViewControllerAnimated(true, completion: nil) stays at VC4.
I found some similar questions:
How to dismiss the current ViewController and go to another View in Swift
How to dismiss VIewController iOS Swift
modal View controllers - how to display and dismiss
However, the sequence segues are different, therefore I can't follow the suggestions.
How can I achieve this? Thanks
UPDATE:
The following code in moviePlayerPlayBackDidFinish properly dismisses VC4 and back to parent VC1 if going from VC1 -> VC4:
self.dismissViewControllerAnimated(true, completion: nil);
However, VC4 does not dismiss when going from VC3 -> VC4.
Finally solved my issue by adding the additional code in moviePlayerPlayBackDidFinish:
self.navigationController?.popViewControllerAnimated(true)
After much research, I found the solution from here: dismissViewControllerAnimated() does not dismiss view controller

Remove view from RAM swift

I'm using segue navigation with self.perfomSegueWithIndentifier function with type of show segues.I noticed after every navigation other view/viewcontroller did not remove from RAM.What I have to do to solve this
This is what you are looking for:
On Button Tap on View Controller 1:
self.navigationController.pushViewController(secondViewController, animated: true)
On Button Tap on View Controller 2:
self.navigationController?.popViewControllerAnimated(true)
Programmatically
Lets say you have VC1 embed in UINavigationController for now VC1 becomes the top view controller on the navigation stack.
So if you are going VC1 -> VC2
self.navigationController?.pushViewController(vc2, animated: true)
In that case VC2 becomes the top view controller on the navigation stack.
and while coming back from VC2 -> VC1 (Button tap on VC2 perform this statement)
self.navigationController?.popViewControllerAnimated(true)
In that case again VC1 becomes the top view controller on the navigation stack.
Using Segue
Just use Show(e.g Push) and set the identifier for that like i have pushView
and invoke
self.performSegueWithIdentifier("pushView", sender: nil) for moving VC1 -> VC2
and for coming back to VC2 -> VC1
use the same self.navigationController?.popViewControllerAnimated(true)

Resources