Performing segue after sender view controller is dismissed - ios

Basically, I have a button in a slide-out menu (which is its own view controller that covers part of the Origin screen, let's call it Menu) that, when pressed, performs a modal segue to another controller, let's say Destination.
Is there any way that upon pressing the button in Menu (to go to Destination), that I can dismiss Menu back to Origin, and THEN segue to Destination?
It sounds silly but it's something that I think I've seen apps do before. In my case, the reason for wanting to do this is that once I press "Done" on Destination, it dismisses that controller back to Menu, when I want it to just dismiss back to Origin. I can't just perform a segue back to Origin from Destination.
Code:
This is how I open the Menu from Origin:
let interactor = Interactor()
#IBAction func openMenu(_ sender: AnyObject) {
performSegue(withIdentifier: "openMenu", sender: nil)
}
#IBAction func edgePanGesture(sender: UIScreenEdgePanGestureRecognizer) {
let translation = sender.translation(in: view)
let progress = MenuHelper.calculateProgress(translationInView: translation, viewBounds: view.bounds, direction: .Right)
MenuHelper.mapGestureStateToInteractor(
gestureState: sender.state,
progress: progress,
interactor: interactor){
self.performSegue(withIdentifier: "openMenu", sender: nil)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationViewController = segue.destination as? MenuViewController {
destinationViewController.transitioningDelegate = self
destinationViewController.interactor = interactor
destinationViewController.currentRoomID = self.currentRoomID
}
}
This is my prepareForSegue from Menu to Destination currently, nothing fancy:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
let inviteVC = segue.destination as! InviteVipViewController
inviteVC.currentRoomID = self.currentRoomID
}
And finally to dismiss Destination is just a simple
#IBAction func cancelButtonPressed(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
I saw this question which is basically what I'm trying to do but there was no answer unfortunately: Performing segue after dismissing modal swift
Sorry if this sounds confusing, but if anyone knows what I'm talking about and can let me know how I can set up the segues/prepareForSegues to make it work, any input would be appreciated!

Based on a modification to this answer, the following should work:
In your storyboard, remove the segue that is triggered by tapping your menu button and goes to Destination.
Create a new segue that goes from the Origin view controller to Destination view controller. This segue is going to be manually performed.
When your Destination option is selected in Menu, have Menu dismiss itself and then perform the Destination segue on Origin, like this:
// This code goes in Menu, and you should call it when
// the menu button is tapped.
//
// presentingViewController is Origin
weak var pvc = self.presentingViewController
self.dismiss(animated: true) {
// Menu has been dismissed, but before it is destroyed
// it calls performSegue on Origin
pvc?.performSegue(withIdentifier: "openDestination", sender: nil)
}
When Destination is dismissed, you should see Origin, without seeing Menu at all.
I tested this in a sample app where "Menu" was not a slide out, but a full modal view controller, and it worked for me.
EDIT: While troubleshooting with #KingTim, he found that we needed to wire the segue from the UINavigationController, not Origin, to the Destination. This is because Origin is inside a navigation controller. After that discovery, it worked.

If your presenting view is embedded in a navigation controller then you can do this:
weak var pvc:UIViewController! = self.presentingViewController?.childViewControllers[0]
dismiss(animated: true)
{
pvc.performSegue(withIdentifier: "SegueID", sender: nil)
}

Simple solution with presentingViewController
if let destinationVC = self.presentingViewController as? YourViewController {
destinationVC.isBooleanPassed = true
destinationVC.selectedString = "here comes your string"
destinationVC.selectedInteger = 12345
}
dismiss(animated: true, completion: nil)

Related

reset variables when pass to an another ViewController

my problem is that when i switch to an another ViewController, my variables of the previous VC call are reset so i can't do what i want after
#IBAction func BackBtn(_ sender: Any) {
self.nbrQst = 10
self.Switch1A = 2
performSegue(withIdentifier: "Numero", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Numero" {
let vc = segue.destination as! ViewController
vc.nbrQt = nbrQst
}
if segue.identifier == "Numero" {
let vcNv = segue.destination as! ViewController
vcNv.Switch1 = Switch1A
}
}
below the way that i send information from my Lvl1 file to the lvl selector file to add a if else for unblocks the lvl
#IBAction func BackBtn(_ sender: Any) {
self.nbrQst = 10
self.Switch1A = 2
performSegue(withIdentifier: "Numero", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! ViewController
vc.nbrQt = nbrQst
vc.Switch1 += 2
}
that's the second ViewController who send the number to the first one, and i want that the first one remember the number and add them up each time I press the button that sends the numbers
Let's call "Selector VC" the main VC.
The reason why those numbers reset in main VC is that when you press the "BackBtn", you're not actually going back to main VC, you're creating a new main VC and going there (because you did performSegue(withIdentifier: "Numero", sender: self) in BackBtn function, performSegue always takes you to a new VC rather than taking you back). The new main VC has all those fields starting from an initial number (I assume initially they're zero).
What you want to do is to go BACK, not forward. Depending on how you went to the level VC from main VC, you have different ways of going back to main VC.
If you're pushing everything onto a navigation controller, then you can go back by:
self.navigationController?.popToRootViewController(animated: true)
If you're presenting everything modally, you can go back by
self.dismiss(animated: true, completion: nil)
You said, you want to update the value in main VC so that you can block/unblock next level. A very standard way to achieve this is via delegate, please Google this ("delegates in Swift") and follow their tutorials there.
I strongly recommend that you take an online course on swift development before you go ahead and develop your own app, because you could potentially do a lot of things wrong and waste a lot of your time trying to get an answer from Stack Overflow.

Having trouble "reaching" my override prepare function to programmatically change views in a tab bar controller

I have a view controller with a container view that has a tab bar controller embedded in it. Im using this to display 3 different view controllers based on what is pressed from a segmented control in the main vc. I have been following this solution: https://stackoverflow.com/a/38283669/11536234
My problem is that when I change the segmented control index (by pressing a different segment) I can't figure out how to "reach" the prepare function.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("prepare reached")
//super.prepare(for: segue, sender: sender)
//switch(segue.identifier ?? "") {
//case "TabBar":
guard let TabController = segue.destination as? UITabBarController else {
fatalError("Unexpected destination: \(segue.destination)")
}
TabController.selectedIndex = toggle.selectedSegmentIndex
//default:
//fatalError("Unexpected Segue Identifier; \(segue.identifier)")
//}
}
#IBAction func toggleAction(_ sender: UISegmentedControl) {
print("toggle is now at index: ", toggle.selectedSegmentIndex)
//performSegue(withIdentifier: "TabBar", sender: sender)
//container.bringSubview(toFront: views[sender.selectedSegmentIndex])
}
So far i have tried placing a performsegue function in an action function linked to the segmented control. This doesn't work, however, because it essentially adds another embedded tab bar programmatically or calls the embed segue again and I receive this error statement: "There are unexpected subviews in the container view. Perhaps the embed segue has already fired once or a subview was added programmatically?"
*The commented lines of code are there to show what I've tried that hasn't worked vs where I'm at.
When you embed a view controller to a container view from another view controller(MainVC), the segue is performed only once when MainVC loads. To pass values to the embedded UIViewController/UITabBarController, you need to get the child view controller and send the data
class MainVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func segmentControlAction(_ sender: UISegmentedControl) {
if let tabBarVC = self.children.first(where: { $0 is UITabBarController }) as? UITabBarController {
tabBarVC.selectedIndex = sender.selectedSegmentIndex
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//called before mainvc viewDidLoad
let destinationVC = segue.destination as? UITabBarController
destinationVC?.selectedIndex = 1
}
}
You can't do what you are trying to do.
With embed segues the segue fires when the host view controller is first loads. That invokes your prepare(for:sender) method, once and only once, before the embedded view controller's views are loaded. It doesn't get called again.
What you need to do is to save a pointer to your child view controller in an instance variable. Define a protocol that the parent uses to talk to the child, and then use that protocol to send a message to the child when the user selects a different segment in your segmented control.
Then the child (presumably the tab bar controller) can switch selected tabs in response to the message you define.

navigation bar disappear after adding prepare function

I am woking on an recording APP.
I tried to add navigation controller in my first recording viewcontroller which then could pass filename array to the second viewcontroller using the following function prepare:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as? MainViewController
controller?.recordArray = recordingArray
self.present(controller!, animated: true, completion: nil)
}
However, when ran in the simulator the navigation bar disappeared in the second controller and Xcode pops out the warning
Thread1: EXC_BAD_ACCESS(code=2,address=0x7fff51edfff8)
Has anyone got any advice?
Thanks!
Don't ever call present(_:animated:completion:) inside prepare(for segue:).
prepare(for segue) is called automatically by the system just before a segue is about to happen to let you prepare the data for sending to the destination viewcontroller or do any other calculations you need to before performing the segue. A segue needs to be set up in Storyboard and it will either be called automatically or if it is a manual segue, you need to call it using perform(segue) and once you do that, the system will call prepare(for segue) for you. You can see why calling another navigation function proves to be problematic, since you are trying to navigate to another viewcontroller using two different methods (segue and present).
If you haven't set up the segue in Storyboard, then you also need to do that, since if it is not set up, let controller = segue.destination as? MainViewController will be nil.
Once you set up the segue in Storyboard, this is how your function should look like:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller = segue.destination as? MainViewController {
controller.recordArray = recordingArray
self.present(controller, animated: true, completion: nil)
}
}
You shouldn’t be trying to present a VC in this method, it’s just a place for you to configure the destination VC before the segue presents it
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let controller = segue.destination as? MainViewController else { return }
controller.recordArray = recordingArray
}

Add Tab Bar Programmatically to ViewController

I have a TabBarController in my Main.storyboard file.
In my Upload.storyboard I am presenting a ViewController from the Main.storyboard file, however, it doesn't contain the tab bar.
The viewProfile button should go to a tab called Sharks and within that present a view controller based on data gathered in the Upload.storyboard (modal view).
Can I add the tab bar programmatically or am I not properly presenting the correct VC?
// MARK: - Actions
#IBAction func viewProfileButtonPressed(_ sender: UIButton) {
let stb = UIStoryboard(name: "Main", bundle: nil)
let sharkProfile = stb.instantiateViewController(withIdentifier: "sharkProfile") as! SharkProfileTableViewController
self.present(sharkProfile, animated: true) {
// add tab bar here?
}
}
What you need to do is present the tab bar view controller, not the view controller that is embedded in it
One method is to create a Delegate Protocol to allow a tap on View Profile button to "call back" to the View Controller that presented it. When that callback is received, the VC sets the "current" tab.
It will look something like this:
// define "call back" delegate protocol
protocol EncounterUploadedDelegate : class {
func didTapSharkProfileButton()
}
The Encounter view controller will need to conform to that protocol:
class EncounterViewController: UIViewController, EncounterUploadedDelegate {
// the normal stuff for this VC and all the other code for it
// ...
// conform to the protocol
func didTapSharkProfileButton() -> Void {
// when we get this call-back, switch to the Shark Profile tab
// tabs are zero-based, so assuming "SharkProfile" is
// is the 4th tab...
self.tabBarController?.selectedIndex = 3
}
// assuming a "Show Modal" segue named "ShowEncounterUploadSegue" is used
// to present the Modal View
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowEncounterUploadSegue" {
let vc = segue.destination as! TabModalVC
vc.encounterDelegate = self
}
}
}
The view controller to be presented as modal:
class TabModalVC: UIViewController {
weak var encounterDelegate: EncounterUploadedDelegate?
#IBAction func profileButtonTapped(_ sender: Any) {
// dismiss self (the modal view)
self.dismiss(animated: true, completion: nil)
// this will call back to the delegate, if one has been assigned
encounterDelegate?.didTapSharkProfileButton()
}
}
Hope that all makes sense :)

Very poor performance after dismissViewController

I have a view controller that presents another one modally which contains a gif and a table view with animation on the cells, and when I dismiss it, the app has an annoyingly long delay before you can do anything again (like 3-5 seconds).
I've found answers that say it's because I still have a reference to the vc after its dismissal, but I don't see how that's possible because the only place I make a reference to it is in prepareForSegue.
Any suggestions?
Edit 1:
Here is my prepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destVC = segue.destinationViewController as! InWorkoutViewController
destVC.workout = workout
}
The destVC's workout property is an optional custom class.
Edit 2:
Here is how the VC is dismissed (still very slow):
#IBAction func tappedX(sender: AnyObject) {
dispatch_async(dispatch_get_main_queue(), { [unowned self] in
self.dismissViewControllerAnimated(true, completion: nil)
})
}

Resources