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)
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.
I have in my project tow view controller and I linked the first VC with navigation controller but the problem is : I used present to go to second VC (that mean I didn't use segue) ... how can I set back to first VC in navigation Controller by code (without segue) .
picture from my storyboard
my code :
let storyboard = self.storyboard
let viewcontroller = storyboard?.instantiateViewController(withIdentifier: "contact_detail") as! ViewController2
viewcontroller.arr2 = arr
present(viewcontroller, animated: true, completion: nil)
With the dismiss method it will work.
Dismisses the view controller that was presented modally by the view controller. (Apple Docs)
self.dismiss(animated: true, completion: nil)
If you are presenting a viewcontroller with present method, you can dismiss it with dismiss method.
If you are adding any view controller with push method then only it will get added to your navigation stack and you can remove it by calling popviewcontroller on it's back action.
Here, you are presenting a viewcontroller, hence it will not get added to your first navigation stack and you can not remove it with pop action on click of back.
If you are looking for a back button like feature on presented view controller, you can add a back button in toolbar, dismiss a viewcontroller on back button action, and can animate it like a popnavigation while dismissing.
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)
}
I solved this by using pushViewController
self.navigationController?.pushViewController(MyViewController, animated: true)
so I have a main view controller that the view controllers will present inFront of each other and I want when user click the button in the last view controller close all of the presented modally view controllers so I used this code But I didn't get the result
let destination = matchViewController()
let appDelegate:UIApplicationDelegate = UIApplication.shared.delegate!
let initialViewController = destination
let navigationController = UINavigationController(rootViewController: initialViewController)
appDelegate.window??.rootViewController = navigationController
appDelegate.window??.makeKeyAndVisible()
I want to use unwind segue to exit But there is another problem too
the last view controller will present many times in many different situations so I just to dismiss all presented modally view controllers in. this situation
I rather not using the navigationController But if I had to use it pleas tell me where exactly should I use that ?
Two options:
Dismiss all view controllers on the root view controller
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
Dismiss all viewControllers until it has a presentingController
func dismissAllControllers() {
guard let vc = self.presentingViewController else { return }
while (vc.presentingViewController != nil) {
vc.dismiss(animated: true, completion: nil)
}
}
I came across a library call Motion on GitHub which provide sets of transition on view and controllers.
Based on the website, to use the animation on navigation controller we need to create a new navigation controller class like below, and I apply the new navigation controller. But when it failed and not pushing to the new view controller.
the GitHub link is https://github.com/CosmicMind/Motion
class AppNavigationController: UINavigationController {
open override func viewDidLoad() {
super.viewDidLoad()
isMotionEnabled = true
motionNavigationTransitionType = .zoom
}
}
then on the main view:
let navigation = AppNavigationController()
navigation.pushViewController(newViewController, animated: false)
If you're using Motion's animated NavigationController throughout your app, you can set it this way in your AppDelegate's didFinishLaunchingWithOptions method.
let newViewController = NewViewController(nibName: "NewViewController",bundle: nil)
let navigation = AppNavigationController(rootViewController: newViewController!)
self.window?.rootViewController = navigation
self.window?.makeKeyAndVisible()
UPDATE:
If you want to create a separate NavigationController and push specific UIViewController to it, then you need to present the navigation controller instead of pushing it. See below.
let motionNavController = AppNavigationController(rootViewController: galleryViewController)
motionNavController.motionNavigationTransitionType = .none
motionNavController.isNavigationBarHidden = true
self.present(motionNavController, animated: true, completion: nil)
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