In this ViewController, it will present a modal view.
After the modal view presented, how to set the following views to over the modal view?
The first view is UIImageView in current ViewController.
The second view is another modal view, in this example, modalViewController2 will display under the first one.
class ViewController: UIViewController {
#IBOutlet weak var img: UIImageView!
override func viewDidAppear() {
super.viewDidAppear()
self.modalViewController = self.storyboard!.instantiateViewController(withIdentifier: "modal") as! ModalViewController
self.modalViewController!.modalPresentationStyle = .overCurrentContext
self.present(self.modalViewController!, animated: true, completion: nil)
self.modalViewController2 = self.storyboard!.instantiateViewController(withIdentifier: "modal2") as! ModalViewController2
self.modalViewController2!.modalPresentationStyle = .overCurrentContext
self.present(self.modalViewController2!, animated: true, completion: nil)
}
}
First presenting a view controller should be in viewDidAppear as in viewDidLoad view's hierarchy is not yet complete , second you can't present 2 modal VCs at the same time , better way to make only one modal VC and construct it with say 2 views and manage hide/show for each one according to your need
Related
Hello I am pretty annoyed with this:
Originally, I had many segues in my storyboard. Each button at bottom of tool bar would segue to various view controllers. As you can imagine, that is a lot of segues for 6 different items on toolbar. After segueing, each action would call self.dismiss and everything would be ok.
Recently, I wanted to clean up the storyboard.
I created the function:
extension UIViewController {
func segue(StoryboardID: String) {
let popOverVC = UIStoryboard(name: "Main", bundle:
nil).instantiateViewController(identifier: StoryboardID)
popOverVC.modalPresentationStyle = UIModalPresentationStyle.fullScreen
popOverVC.modalTransitionStyle = .crossDissolve
self.dismiss(animated: false, completion: nil)
print("dismissed")
self.present(popOverVC, animated: false, completion: nil)
print("presented")
}
}
What I am seeing is that the dismiss dismisses the new view controller from appearing. It essentially goes back to my first view controller presented upon launch. I want to dismiss all view controllers so that I don't keep piling up my views.
Thanks
The problem is that you present your popOverVC from a view controller that is being dismissed, so your popOverVC will never be shown as its parent is being dismissed.
Not sure how your architecture is exactly but you would either need to use the delegate pattern to tell the parent when to segue, for example:
self.dismiss(animated: true) {
self.delegate?.didDismiss(viewController: self)
}
Or to use some trick to get the top most view controller to present your popOverVC after the current view has been dismissed, ex:
self.dismiss(animated: true) {
var topController: UIViewController = UIApplication.shared.windows.filter{$0.isKeyWindow}.first!.rootViewController!
while (topController.presentedViewController != nil) {
topController = topController.presentedViewController!
}
topController.present(popOverVC, animated: true)
}
But I agree with #Paulw11, a UITabBarController seem like a much better option for what you're trying to do.
This question already has answers here:
Dismissing multiple modal view controllers at once?
(7 answers)
Closed 4 years ago.
I recreated the question and describe its essence more precisely.
I have two view controllers presented modally on Swift 4, without storyboard (can't use unwind) and without navigation controller
A presents B which presents C.
The reason why we don't use navigation controller, it's because we what simple animation from bottom to top and instead of breaking the standard animation from right to left, we decided to use present.
I would like to dismiss 2 view controllers and go from C to A.
Please, don't mark this question as duplicate before you read my question carefully. I found a tone of similar post, but neither solved my problem. Some of them Objective-C or some of the suggest to use:
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
Or:
self.presentingViewController?.dismiss(animated: false, completion: nil)
self.presentingViewController?.dismiss(animated: true, completion: nil)
It's works, but it create weird animation. It's just delete C and animate dismiss for B:
Expected result:
Idea: you need to dismiss third controller with animation and after dismissing you need to dismiss second without animation. While third controller is being dismissed, second shouldn't be visible.
First, set Presentation style of second view controller to Over Current Context when you're presenting it (since we will need to hide its view when we will dismiss third controller)
let vc2 = VC2()
vc2.modalPresentationStyle = .overCurrentContext
present(vc2, animated: true)
continue with creating callbacks properties for willDismiss and didDismiss inside third controller. This callback will be called before and after you dismiss third controller
class VC3: UIViewController {
var willDismiss: (() -> Void)?
var didDismiss: (() -> Void)?
#IBAction func dismissButtonPressed(_ sender: UIButton) {
willDismiss?()
dismiss(animated: true) {
self.didDismiss?()
}
}
}
then in second view controller in the place where you present third view controller, set third controller's callback properties: declare what happens when third controller will dismiss: you need to hide view of second and then after dismissing third you need to dismiss second without animation (view can stay hidden since view controller will be deinitialized)
class VC2: UIViewController {
#objc func buttonPressed(_ sender: UIButton) {
var vc3 = VC3()
vc3.willDismiss = {
self.view.isHidden = true
}
vc3.didDismiss = {
self.dismiss(animated: false)
}
present(vc3, animated: true)
}
}
Anyway, second option is using UINavigationController and then just call its method popToViewController(_:animated:).
Create a snapshot from the currently visible view and add it as a subview to the first presented view controller. To find that you can simply "loop through" the presenting view controllers and dismiss from the initial one:
#IBAction func dismissViewControllers(_ sender: UIButton) {
var initialPresentingViewController = self.presentingViewController
while let previousPresentingViewController = initialPresentingViewController?.presentingViewController {
initialPresentingViewController = previousPresentingViewController
}
if let snapshot = view.snapshotView(afterScreenUpdates: true) {
initialPresentingViewController?.presentedViewController?.view.addSubview(snapshot)
}
initialPresentingViewController?.dismiss(animated: true)
}
This is the result with slow animations enabled for the dismissal:
https://www.dropbox.com/s/tjkthftuo9kqhsg/result.mov?dl=0
If you are not using a navigation controller and you want to dismiss all ViewControllers to show the root ViewController (assuming A is the root):
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
Use this in button action method.
The current VC will dismiss when you dismiss the parent VC.
This will dismiss both VCs in single animation.
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
I have a UILabel in my ViewController that has a NavigationController (let's say view controller A) with a tap gesture recognizer attached to the label. When the label is tapped another view appears (let's call it B). The user picks some text in B and the view dismisses back to A with the label text updated with the selection. So I created a delegation between A and B to get the selection. The problem is that I do not see the NavigationBar when B appears. Is there a way to fix this?
ViewController A
#IBOutlet weak var sectionName: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let sectionLabelTap = UITapGestureRecognizer(target: self, action: #selector(labelTapped(_:)))
sectionName.isUserInteractionEnabled = true
sectionName.addGestureRecognizer(sectionLabelTap)
}
#objc func labelTapped(_ sender: UITapGestureRecognizer) {
let sectionNameVC = storyboard?.instantiateViewController(withIdentifier: "SectionName") as! SectionNameTableViewController
sectionNameVC.selectionNameDelegate = self
sectionNameVC.userData = userData
present(sectionNameVC, animated: true, completion: nil)
}
In order to display the Navigation bar the UIViewController needs to have a UINavigationController.
You can add that sectionNameVC ViewController into a UINavigationController to persevere the present animation.
In that case your code might look something like this:
#objc func labelTapped(_ sender: UITapGestureRecognizer) {
let sectionNameVC = storyboard?.instantiateViewController(withIdentifier: "SectionName") as! SectionNameTableViewController
sectionNameVC.selectionNameDelegate = self
sectionNameVC.userData = userData
let naviagtionController = UINavigationController(rootViewController: sectionNameVC)
present(naviagtionController, animated: true, completion: nil)
}
Or you can simply call pushViewController on the View Controller A's navigation Controller, like this:
self.navigationController?.pushViewController(sectionNameVC, animated: true)
This will add sectionNameVC into the View Controller A's navigation Controller stack. In this case the transition animation will be different, the sectionNameVC will come from your right.
You are missing the concept between "Presenting" View Controller & "Navigating" the View Controller. You will get the answer, once you understood the concept. Here, it is..
When you are presenting the ViewController, you are completely replacing the stack container to the new view controller.
STACK holds the addresses of the ViewControllers you push or pop via navigating.
e.g:
present(sectionNameVC, animated: true, completion: nil)
On the other hand, if you are navigating to other view controller by pushing it. In this case, you can go back to previous controller by simple popping the ViewController address from stack.
e.g:
self.navigationController?.pushViewController(sectionNameVC, animated: true)
self.navigationController?.popViewController(animated: true)
So, If you navigate then, only you will get navigation Bar.
Now, in your case, you are presenting the ViewController and hence, navigation bar is not showing.
I want to present two view controllers in a row, without seeing the first one.
I want to be able to return from the second one to the first.
I have read this post, but the responses focus on using the navigation controller, while I want to present the second view controller modally.
The use case is: My initial VC check if the user is logged in, and presents the login VC if not.
If yes, it displays the main VC.
On logout, I should be able to unwind to the login VC.
A suitable solution would be presenting the second view controller first and only presenting the first one after the second one fully appeared.
InitialViewController
let secondVc = storyboard!.instantiateViewController(withIdentifier: "Second") as! SecondViewController
secondVc.initialVc = self
present(secondVc, animated: true)
SecondViewController
fileprivate var firstViewDidAppearTime = true
var initialVc: InitialViewController!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if firstViewDidAppearTime {
firstViewDidAppearTime = false
let firstVc = storyboard!.instantiateViewController(withIdentifier: "First")
initialVc.present(firstVc, animated: false)
}
}
I'm trying to present a view controller modally and get the famous Presenting view controllers on detached view controllers is discouraged error. I researched it and the consensus solution appears to be making the presentation from the parent, which I tried but did not have success with. I suspect the problem is because the navigation controller was instantiated from a struct as a static property (to make it easier for other view controller's to pop to root as this is what the UX called for).
struct SectionNavigationControllers {
static var one = SectionNavigationController()
static var two = SectionNavigationController()
static var three = SectionNavigationController()
static var four = SectionNavigationController()
}
And here is where one of the navigation controllers is created (using this struct):
let SectionOneRoot = MasterSearchViewController()
func addNavigationController() {
self.addChildViewController(SectionOneRoot)
SectionOneRoot.didMove(toParentViewController: self)
SectionNavigationControllers.one = SectionNavigationController(rootViewController: SectionOneRoot)
view.addSubview(SectionNavigationControllers.one.view)
}
And so when I try to present a view controller modally from MasterSearchViewController (the root view controller), I get the said error.
navigationController?.present(Random200ViewController(), animated: true, completion: nil)
Ideas?
Here's a convenience function you can add to any piece of code to present a view controller from anywhere in your app:
func showModally(_ viewController: UIViewController) {
let window = UIApplication.shared.keyWindow
let rootViewController = window?.rootViewController
rootViewController?.present(viewController, animated: true, completion: nil)
}
I hope it helps!
If you want to present it on your app's root viewController, you can do it like this:
let rootVC = UIApplication.shared.keyWindow?.rootViewController
rootVC?.present(Random200ViewController(), animated: true, completion: nil)