Swift unwind modal - ios

I'm working on an app where I have an error page displayed incase there is a network problem. The app has several storyboards, and this can happen anywhere.
func displayErrorPage(errorCode: ErrorCode) -> Void {
if !isDisplayingError {
DispatchQueue.main.async {
self.isDisplayingError = true
let storyboard = UIStoryboard(name: "alert", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "AlertScreen") as! AlertVC
controller.errorCode = errorCode
UIApplication.shared.topViewController?.present(controller, animated: true, completion: nil)
}
}
}
I would like to use unwind to dismiss it, to clear out any views and get back to the home page. I have used this in the alert view to close, but sometimes doesn't work.
#IBAction func closeBtn(_ sender: Any) {
flowError = true
NetworkManager.shared().isDisplayingError = false
performSegue(withIdentifier: "unwindToSH", sender: self)
}
Anyone with some pointers?

Create the unwind segue IBAction in the view controller you want to unwind to. 
Perhaps in your case it is HomeViewController. Add this code inside your HomeViewController
#IBAction func unwindToHomeVC(segue: UIStoryboardSegue) {}
Wire up the unwind segue - Control drag from AlertVC’s ViewController icon to exit icon in the storyboard choose the unwind segue action (unwindToHomeVC) that was created in step 1 from the list of IBActions.
Specify a segue Identifier - specify its identifier in the Attributes Inspector of the Utilities Pane. Ex: “unwindToHomeVC”
Trigger unwind segue programmatically - trigger that in the appropriate place. In your case close button action
self.performSegue(withIdentifier: "unwindToMenu", sender: self)
It will work even if you work with multiple storyboard as well, if your setup is right. I hope that this one would help you.
Thanks

Related

If I present a ViewController programmatically without using a Navigation Controller, does the new VC "replace" the old one, or does it stack on top?

I'm new to iOS.
I have an app where the path through the app can vary depending on the configuration I fetch from an API. Because of this, I don't use segues because I would need to create a segue from each ViewController (VC) to EVERY other VC. It creates a mess of segues that I don't want. So Instead I navigate from screen to screen like this:
func navigate(to viewController: String) {
let storyboard = UIStoryboard(name: K.mainStoryBoard, bundle: nil)
let nextVC = storyboard.instantiateViewController(identifier: viewController)
self.present(nextVC, animated: true, completion: nil)
}
My question is this: If I would have embedded my VCs in a NavigationController I know it would have created a stack. When I get to the last screen I would call func popToRootViewController(animated: Bool) -> [UIViewController]? and start from the beginning. However, I don't use a NavigationController. So, does that mean that when I present the next VC it replaces the previous one or does it stack on top of the previous one? I'm trying to prevent memory leaks so I want to make sure I don't keep stacking the VCs on top of each other until my app runs out of memory and crashes.
Thanks in advance
Edit
So, in my final VC I created an unwind segue. And I call it like this: performSegue(withIdentifier: "unwindToMain", sender: self)
and In my first VC (the initial VC in my app) I write this:
#IBAction func unwind( _ seg: UIStoryboardSegue) {
}
Everything works fine the first trip through the app. The last VC unwinds back to the fist VC. The problem is now that when I try to run through the app again (starting from VC 1 and then going to the next one) I now get this error:
MyApp[71199:4203602] [Presentation] Attempt to present <MyApp.DOBViewController: 0x1038760c0> on <MyApp.ThankYouViewController: 0x112560c30> (from <MyApp.ThankYouViewController: 0x112560c30>) whose view is not in the window hierarchy.
To make sense of this, DOBViewController would be the second VC I want to go to from the MainVC. ThankYouViewController is my last VC. It looks as if it isn't completely removed from the stack. Can anyone tell me what's going on?
Here is a very simple, basic example...
The controllers are setup in Storyboard, each with a single button, connected to the corresponding #IBAction.
The DOBViewController has its Storyboard ID set to "dobVC".
The ThankYouViewController has its Storyboard ID set to "tyVC".
MainVC is embedded in a navigation controller (in Storyboard) and the navigation controller is set to Initial View Controller:
class MainVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.setNavigationBarHidden(true, animated: false)
}
#IBAction func pushToDOB(_ sender: Any) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "dobVC") as? DOBViewController {
navigationController?.pushViewController(vc, animated: true)
}
}
}
class DOBViewController: UIViewController {
#IBAction func pushToTY(_ sender: Any) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "tyVC") as? ThankYouViewController {
navigationController?.pushViewController(vc, animated: true)
}
}
}
class ThankYouViewController: UIViewController {
#IBAction func popToRoot(_ sender: Any) {
navigationController?.popToRootViewController(animated: true)
}
}
does that mean that when I present the next VC it replaces the previous one or does it stack on top of the previous one?
The new one stacks on top of the previous one.
When you present a view controller, like
self.present(nextVC, animated: true, completion: nil)
The one you called .present on (self in this case) becomes presentingViewController for the nextVC instance.
The one you presented (nextVC in this case) becomes presentedViewController for the self instance.

Segue not Identified

I want to switch between viewController by using containerViewController.
For this I created multiple segues and viewController.
when I run
func segueIdentifierReceivedFromParent(_ identifier: String){
self.segueIdentifier = identifier
self.performSegue(withIdentifier: self.segueIdentifier, sender: nil)
}
in it's parent controller file it works fine but when I called it through other controller file it gives error
ContainerViewController: 0x7fd703707b40>) has no segue with identifier 'second''
This is how i called it in other viewController
let vc = ParentViewController()
vc.segueIdentifierReceivedFromParent("second")
Here "second" is the Identifier given to segue in storyBoard
Above code is written in AddTarget of button.
So when I tap button I get Error
If you are in same a storyBoard then you can simply do this -
Take a button in your firstVC and ctrl + drag a segue upto your secondVC. Then click on the segue. (the round thing on between two view controller). -
And set a identifier for that (here about is the identifier, you can give any name for this)-
Here is the code -
#IBAction func UserDetailVC(sender: AnyObject) {
self.performSegue(withIdentifier: "segueIdentifierName", sender: sender)
}

Swift - Open new View after click on Button

I would like to open new View after clicking on a button. It works, when I make it by UIButton in Main.storyboard, however, I need to make it in code, because I have to use some if-statements with login/password. I tried to make it like it was suggested in other similar questions, but it doesn't work:
#IBAction func login(_ sender: UIButton) {
performSegue(withIdentifier: "Second", sender: self)}
Id of login-view: First;
Id of the second view: Second
You can use storyboard Id
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "secondStoryboardId") as! SecondViewController
self.present(nextViewController, animated:true, completion:nil)
Your Segue doesn't look to have an identifier in the screenshot. Can you confirm. Please add an identifier to your segue from first view to second and then alter the below code with the name of segue identifier
performSegue(withIdentifier: "Segueidentifier", sender: self)}
To add identifier on the segue, click on the segue in your storyboard and then on the right hand side in the Attributes Inspector add identifier in the Storyboard Segue section
You shouldn't use storyboard ID for segues. There will be segue identifier separately. When you drag&drop segue you will get one line connected between the screens. Click on that to add segue identifier. You need to provide that segue ID here
You can see segue connection from the below image:
Set Segue identifier as below :
Select the segue and give a Identifier/Name ( Here , I've put toSecond) at Attribute Inspector
and then perform segue .
ScreenShot :
Source Code :
#IBAction func login(_ sender: UIButton) {
performSegue(withIdentifier: "toSecond", sender: self)
}
Also make sure that your segue is from the initial VC not the button...

Performing segue after sender view controller is dismissed

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)

Show Segue uses the wrong presentingViewController, and causes wrong navigation

I am trying to fix up an edit segue (show in IB) where I can click the 'Edit report details' button on the toolbar and it will Show Segue towards the 'Configure Report' View controller.
However, if I click cancel, it goes all the way back to my login screen, because presentingViewController is a UINavigationController, even though it shouldn't be.
Here's the story board. http://i.imgur.com/DK4HhpO.png
Any ideas?
// MARK: Navigation
#IBAction func cancel(sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways.
let isPresentingInAddItemMode = presentingViewController is UINavigationController
if isPresentingInAddItemMode {
dismissViewControllerAnimated(true, completion: nil)
}
else {
// In this mode (push presentation), we need to pop the view controller to get rid of it, rather than dismissing
navigationController!.popViewControllerAnimated(true)
}
}
This is all I do in my code and it works.
#IBAction func cancel(sender: AnyObject) {
self.navigationController?.popViewControllerAnimated(true)
}
How ever you may need to check for which segue identifier sent you first.
#IBAction func cancel(sender: AnyObject) {
if (segue.identifier == "Edit") {
self.navigationController?.popViewControllerAnimated(true)
} else if (segue.identifier == "Add") {
self.navigationController?.popViewControllerAnimated(true)
}
This way it knows which one to follow. It also may depend on how you are segue to the view in the first place.

Resources