I have a Main Tab Bar Controller with 3 Views.
Main View
History View
Menu View
When my App first loads, the Tab Bar Controller checks if the user is Logged On, if she is not, it segues to a Logon view. Once the user Logs On I call an Unwind Segue from the Logon View as follows:
self.performSegueWithIdentifier("unwindToTabBarFromSignInView", sender: self)
This brings the user to the Unwind Segue of the Menu View in my Tab Bar controller since this is the View with the Logout button, thus it was the View that was presented to the User last. However, I would like to present the Main View instead of the Menu View once the user Logs On. To accomplish this I have the following code in the Unwind Segue. Unfortunately, it seems to have no effect, the user keeps being brought back to the Menu View once they Log On instead of the Main View.
#IBAction func unwindToTabBarFromSignInView(segue: UIStoryboardSegue) {
tabBarController?.selectedIndex = 0
}
It's curious that that doesn't work. But I have an easier solution.
When unwinding to a UITabBarController, every tab's ViewController can implement its own unwind #IBAction and you can do directly to the tab you want.
In your MainViewController implement this:
#IBAction func unwindToMainViewController(_ segue: UIStoryboardSegue) {
print("back in MainViewController")
}
Create the unwind segue by control-dragging from the ViewController icon to the Exit icon and give it an identifier "unwindToMain". Once the user logs on, simply call:
self.performSegueWithIdentifier("unwindToMain", sender: self)
and you will return to the Main tab of your UITabBarController.
Related
My app's root is a UITabBarController with 5 sections, each of them contains a UINavigationController.
I also want to add a chat feature in the app, that could be accessed with a rightBarButton present in every navigation bar of the app. I would like it to show a chat UIViewController on the screen, unselecting the currently selected tab bar item and without losing the navigation state of the five navigation controllers, even the one that was previously selected before tapping the chat button. What would be my best bet to do it?
Thanks for your help/ideas.
Step 1: In your storyboard add a ChatViewController
- Embed your ChatViewController in Navigation View Controller if you wanna have a navigation bar. Add a close BarButtonItem in your ChatViewController.
Step 2: Create a close Action in your ChatViewController and binding with BarButtonItem in the StoryBoard.
#IBAction func CloseAction(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
}
Step 3:
In storyboard, select the Navigation Controllers with the rightBarButton and choose Present Modally and connect to Navigation Controller of the ChatViewController.
You can go to the ChatViewController without losing the navigation state of any navigation controller.
I'm studying the tutorial from Apple Developer: Start Developing iOS Apps (Swift), and I'm confused with push and modal segue.
There are two scenarios, Save and Cancel button in navigation bar, backing to scene 1 from scene 2.
If the Cancel button is pressed, it will call different method for dismissing scene 2:
#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 isPresentingInAddMealMode = presentingViewController is UINavigationController
if isPresentingInAddMealMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
else {
fatalError("The MealViewController is not inside a navigation controller.")
}
}
In this method, if the scene is presented by modal segue, dismiss(animated:completion:) is called, and
if the scene is presented by push segue, popViewController(animated:) is called for poping up the ViewController from the navigation stack.
But for the Save button, the tutorial overrides a method in scene 2, prepare(for:sender:), and a action method in scene 1, unwindToMealList(sender:).
And it drag the Save button to Exit (the button in the scene dock) and choose unWindToMealList(sender:) method.
So the flow will be: prepare(for:sender:) -> scene 2 dismissed and scene 1 presented -> unWindToMealList(sender:)
I'm wondering that the code snippets didn't dismiss explictly scene 2 and remove the ViewController in navigation stack when Save button is pressed.
I know that modal segue won't push ViewController to navigation stack, but push segue will push it.
Why the code snippets doesn't pop up it from navigation stack?
Many thanks.
It seems like the tutorial you are reading is making use of unwind segues.
Unwind segues, just like normal segues, have a source and a destination and you can prepare for it in prepareForSegue, but instead of presenting the destination VC, it will dismiss the source VC so that the destination VC is shown.
Unwind segues behave differently in different situations. When you present VC B from VC A using a push segue, and an unwind segue from B to A, the unwind segue will pop VC B from the navigation stack. When you present VC B from VC A modally, the unwind segue will dismiss the modally presented VC.
As you can see, unwind segues are quite smart. It will decide for itself what to do in order to show the destination VC. It can even pop two or more VCs in the navigation stack!
My target include a lot view need to present different view modally base on each user action. Here what I want to do to get cleaner view hierarchy and better user experience.
Root View Controller present First View Controller modally
When I clicked button on the First View Controller, then the Second View Controller appear modally over it.
As soon as the Second View Controller did appear, I want to dismiss or remove the first one from view hierarchy.
Can I do that? If so, how should i do it?
If not, what is the right way to solve this out cause I will present many modally presented view controllers over each view. I think even if I want to dismiss current view, the previous one will still remain appear when current one dismiss.
UPDATE :
VC1 (Root) > VC 2 (which was present modally) > VC 3 (which was
present modally over VC 2)
When i dismiss VC3, the VC2 is still on view memory. So, I don't want to appear VC2 as soon as I dismiss VC3 and instead I want to see VC1 by removing or dismissing VC2 from view hierarchy.
WANT : At the image, when I dismiss the blue,I don't want see the pink in my view memory and I want to remove it as soon as the blue one appear.
That's what i want to do.
Any Help?Thanks.
So, let's assume that you have a storyboard similar to:
What should happens is:
Presenting the the second ViewController (from the first ViewController).
Presenting the the third ViewController (from the second ViewController).
dismissing to the first ViewController (from the third ViewController).
In the third ViewController button's action:
#IBAction func tapped(_ sender: Any) {
presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
}
As you can see, by accessing the presentingViewController of the current ViewController, you can dismiss the previous hierarchy of the view controllers:
The view controller that presented this view controller.
By implementing presentingViewController?.presentingViewController? that means that: the presented of the presented current ViewController :)
It might seem a little bit confusing, but it is pretty simple.
So the output should be like (I added background colors to the viewControllers -as vc1: orange, vc2: black and vc3: light orange- to make it appears clearly):
EDIT:
If you are asking to remove the ViewController(s) in the middle (which in this example the second ViewController), dismiss(animated:completion:) does this automatically:
If you present several view controllers in succession, thus building a
stack of presented view controllers, calling this method on a view
controller lower in the stack dismisses its immediate child view
controller and all view controllers above that child on the stack.
When this happens, only the top-most view is dismissed in an animated
fashion; any intermediate view controllers are simply removed from the
stack. The top-most view is dismissed using its modal transition
style, which may differ from the styles used by other view controllers
lower in the stack.
Referring to what are you asking:
I think even if I want to dismiss current view, the previous one will
still remain appear when current one dismiss.
I think that appears clearly on the UI (and I find it ok), but as mentioned in the dismiss documentation discussion, both the third and the second will be removed from the stack. That's the right way.
Here is my opinion in different perspective,
Root View Controller present Second View Controller
Add FirstView onto Second View
Dismiss FirstView Controller when button pressed.
Second View Controller,
class ViewController: UIViewController, FirstViewControllerProtocol {
weak var firstViewController: FirstViewController?
override func viewDidLoad() {
super.viewDidLoad()
print("Not initiated: \(firstViewController)")
firstViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "FirstViewController") as? FirstViewController
addChildViewController(firstVC!)
firstViewController?.delegate = self
view.addSubview((firstViewController?.view)!)
print("Initiated: \(firstViewController)")
}
func dismiss() {
firstViewController?.view.removeFromSuperview()
firstViewController?.removeFromParentViewController()
}
}
FirstViewController,
protocol FirstViewControllerProtocol {
// Use protocol/delegate to communicate within two view controllers
func dismiss()
}
class FirstViewController: UIViewController {
var delegate: FirstViewControllerProtocol?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func dismiss(_ sender: Any) {
delegate?.dismiss()
}
deinit {
print("BYE")
}
}
What you want is an "unwind segue":
https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/UsingSegues.html#//apple_ref/doc/uid/TP40007457-CH15-SW8
https://developer.apple.com/library/archive/technotes/tn2298/_index.html
It allows you to dismiss multiple view controllers at the same time, without having to know how many there are in the stack.
In VC1 you would implement an IBAction called (for instance) unwindToRoot. Then in the storyboard for VC3, you wire up your Done button to the Exit object and choose the unwindToRoot action.
When that button is pressed, the system will dismiss all the view controllers it needs to bring you back to VC1.
This is better than calling presentingViewController?.presentingViewController?.dismiss(), because VC3 doesn't need to know anything about the view controller hierarchy underneath it.
So I have this code on a page:
#IBAction func showKaart(sender: AnyObject) {
performSegueWithIdentifier("menuToKaart", sender: sender)
}
And the segue "menuToKaart" does go to the right place but the problem is that it's not using the Navigationcontroller because it's 1 page up. Because of that I am not able to go back.
(sorry if the picture is unclear)
What I am trying to do is this:
Inside ViewController2 is a button called "Kaart" which leads to the viewcontroller "Kaart"
I want a button inside Table View "Locaties" to use the same segue as the button "Kaart" in ViewController2. This works with the code I am using but it doesn't use the navigation controller so there is no navigationbar where I can press back to go back to "ViewController2"
I use a push segue to transition from a uisearchcontroller located within my root view controller, to a second view controller. When I try to use an unwind segue method to transition back to the root view controller from my second view controller, my app does not transition unless the button connected to the unwind method is pressed twice. The unwind method is called both times, however the transition only occurs upon the second call. I do not know why this occurs. Any help is appreciated. Thanks!
Unwind segue method
#IBAction func saveWordAndDefinition(segue:UIStoryboardSegue) {
self.searchController.active = false
if let definitionViewController = segue.sourceViewController as? DefinitionViewController {
saveWordToCoreData(definitionViewController.word)
}
tableView.reloadData()
}
How I linked my segue
Unwind segue
While what you're doing is permissible, it seems to be against best practice. The functionality of presenting a view controller, UITableViewController in this case, entering information, then later dismissing it with a button in the upper-right hand corner is generally associated with a modal view. In a push segue you'll get the back button in the upper-left corner for free, which will enable to you to pop the view controller off the stack without writing extra code.
Here's another Stack Overflow question that describe: What is the difference between Modal and Push segue in Storyboards?
To answer your question specifically, here are a couple links that should help:
[self.navigationController popViewControllerAnimated:YES]; is probably what you're looking for.
Dismiss pushed view from within Navigation Controller
How can I dismiss a pushViewController in iPhone/iPad?
So here's how I finally got this to work:
In my FirstViewController (the vc i'm unwinding to):
Here is my unwind segue method.
#IBAction func saveWordAndDefinition(segue:UIStoryboardSegue) {
self.navigationController?.popViewControllerAnimated(false)
}
Then I gave my unwind segue the identifier "unwind" in Storyboard.
In my SecondViewController (the vc i'm unwinding from):
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "unwind" {
if let destination = segue.destinationViewController as? VocabListViewController {
destination.saveWordToCoreData(word)
destination.tableView.reloadData()
}
}
}
I took care of passing data in the prepareForSegue method of my SecondViewController. Thanks to #Lory Huz for the suggestion. I finally figured out what you meant by it.
Works without any errors!