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 case not occurs on iPhone 6 but, occurs on iPhone 8 or higher. Using a segue of kind Show Detail to navigate from ViewController1 to ViewController2 there are a space showing the previous ViewController1 on top screen like image below:
How can I hide this space?
I tried:
Use all presentation types on ViewController2 (Automatic, FullScreen...)
Size master
You should try presenting it programatically as (set it in previous view controller):
let navVC = UINavigationController.init(rootViewController: vc)
navVC.modalPresentationStyle = .fullScreen
present(navVC, animated: true, completion: nil)
If you don't want to use navigation controller then skip first step.
Set the modalPresentationStyle of the UIViewController you're presenting as fullScreen.
class PresentingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
modalPresentationStyle = .fullScreen
}
}
Here is how you can do it in storyboard...First tap the segue...first change kind to Modal... And then presentation to full screen ...
You could try:
let nav = UINavigationController()
let ctrl = ViewController1()
nav.pushViewController(ctrl, animated: true)
nav.modalPresentationStyle = .overCurrentContext
self.showDetailViewController(nav, sender: self)
nav.popoverPresentationController?.sourceView = self.view
This covers the complete screen when presenting your controller.
nav.popoverPresentationController?.sourceView = self.view
sets the source view of your popover to the size of the view.
Currently I create a custom popup A then when a button in it is pressed dismiss it and in the completion handler create a new popup B.
Both popups are similar in that they use a black view with alpha set to a value to simulate the grayed out screen affect that a standard alert provides.
However dismissing A before creating B causes a flicker. I would like to create B before dismissing A but haven't found a good way to do this.
Ideas on how to do this and avoid the flicker?
I'm thinking of putting the black view with the alpha set in the view controller screen that shows popup A and turning it on when popup A is shown and off when popup B is dismissed. This doesn't seem like the best solution however. Another way would be to have a single popup and show hide controls but this seems not a good solution either because it makes the storyboard VC messy.
Here's how the code looks now:
From a menu the popup A is created:
let storyBoard = UIStoryboard(name: "MenuStoryboard", bundle: nil)
let aPopup = storyBoard.instantiateViewController(withIdentifier: "Popup_A")
present(aPopup, animated: false, completion: nil)
Then from button action of Popup A:
self.dismiss(animated: false) {
let storyBoard = UIStoryboard(name: "MenuStoryboard", bundle: nil)
let bPopup = storyBoard.instantiateViewController(withIdentifier: "PopUp_B")
if let topViewController = UIApplication.shared.topMostViewController {
topViewController.present(bPopup, animated: false, completion: nil)
}
}
And the extensions:
extension UIViewController {
var topMostViewController : UIViewController {
if let presented = self.presentedViewController {
return presented.topMostViewController
}
if let navigation = self as? UINavigationController {
return navigation.visibleViewController?.topMostViewController ?? navigation
}
if let tab = self as? UITabBarController {
return tab.selectedViewController?.topMostViewController ?? tab
}
return self
}
}
extension UIApplication {
var topMostViewController : UIViewController? {
return self.keyWindow?.rootViewController?.topMostViewController
}
}
The way you do is not recommended, a View Controller should not be concerned of "dismissing itself" and then "asking" another view controller to present a new view controller.
I'd use delegation from PopupA to communicate back to whatever object presented it. That object SHOULD dismiss the PopupA view controller.
Once completed, you could try to display PopupB.
I have a stack of UIViewControllers like A -> B -> C. I want to go back to controller A from C. I'm doing it with below code:
DispatchQueue.global(qos: .background).sync {
// Background Thread
DispatchQueue.main.async {
self.presentingViewController?.presentingViewController?.dismiss(animated: false, completion: {
})}
}
It works but controller B seen on screen although I set animated to false. How can I dismiss two UIViewControllers without showing the middle one (B)?
P.S: I can't just directly dismiss from root controller and also I can't use UINavigationController
I searched the community but can't find anything about the animation.
Dismiss more than one view controller simultaneously
Try this.
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
Created a sample storyboard like this
The yellow view controller is type of ViewController and the button action is as follows
#IBAction func Pressed(_ sender: Any) {
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
}
Output
I've created example for dismissing B controller before showing C controller. You can try it.
let bController = ViewController()
let cController = ViewController()
aController.present(bController, animated: true) {
DispatchQueue.main.asyncAfter(wallDeadline: .now()+2, execute: {
let presentingVC = bController.presentingViewController
bController.dismiss(animated: false, completion: {
presentingVC?.present(cController, animated: true, completion: nil)
})
})
}
But on my opinion solution with using navigation controller would be the best for the case. For example you can put just B controller into navigation controller -> present the navController onto A controller -> then show C inside the navController -> then dismiss from C controller whole navController -> And you will see A controller again. Think about the solution too.
Another solution
I've checked another solution.
Here extension which should solve your problem.
extension UIViewController {
func dissmissViewController(toViewController: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
self.dismiss(animated: flag, completion: completion)
self.view.window?.insertSubview(toViewController.view, at: 0)
dissmissAllPresentedControllers(from: toViewController)
if toViewController.presentedViewController != self {
toViewController.presentedViewController?.dismiss(animated: false, completion: nil)
}
}
private func dissmissAllPresentedControllers(from rootController: UIViewController) {
if let controller = rootController.presentedViewController, controller != self {
controller.view.isHidden = true
dissmissAllPresentedControllers(from: controller)
}
}
}
Usage
let rootController = self.presentingViewController!.presentingViewController! //Pointer to controller which should be shown after you dismiss current controller
self.dissmissViewController(toViewController: rootController, animated: true)
// All previous controllers will be dismissed too,
// but you will not see them because I hide them and add to window of current view.
But the solution I think may not cover all your cases. And potentially there can be a problem if your controllers are not shown on whole screen, all something like that, because when I simulate that transition I don't consider the fact, so you need to fit the extension maybe to your particular case.
This is my first application for iOS.
So I have a UIVIewController with a UITableView where I have integrated a UISearchBar and a UISearchController in order to filter TableCells to display
override func viewDidLoad() {
menuBar.delegate = self
table.dataSource = self
table.delegate = self
let nib = UINib(nibName: "ItemCellTableViewCell", bundle: nil)
table.registerNib(nib, forCellReuseIdentifier: "Cell")
let searchButton = UIBarButtonItem(barButtonSystemItem: .Search, target: self, action: "search:")
menuBar.topItem?.leftBarButtonItem = searchButton
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
return controller
})()
self.table.reloadData()
}
I am using also a modal segue in order to open the element's ViewController where I will display details of the element.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.index = indexPath.row
self.performSegueWithIdentifier("ItemDetailFromHome", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "ItemDetailFromHome") {
let settingsVC = segue.destinationViewController as! ItemDetailViewController
settingsVC.parent = self
if self.isSearching == true && self.searchText != nil && self.searchText != "" {
settingsVC.item = self.filteredItems[self.index!]
} else {
settingsVC.item = self.items[self.index!]
}
}
}
That works fine until I try to display the ItemDetailViewController for a filtered element (through the UISearchController).
I have the following message :
Warning: Attempt to present <ItemDetailViewController: *> on <HomeViewController: *> which is already presenting (null)
At every time I am going to the ItemDetailViewController.viewDidLoad() function but after that when the search is activated I have the previous error.
Any idea ? I have tried to use the following async dispatch but without success
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.index = indexPath.row
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.performSegueWithIdentifier("ItemDetailFromHome", sender: self)
})
}
I have found out a solution.
I have add the following code in HomeViewController.viewDidLoad and that works !
definesPresentationContext = true
In my case, I found my code to present the new viewController (a UIAlertController) was being called twice.
Check this before messing about with definesPresentationContext.
In my case, I tried too early to show the new UIViewController before closing the previous one. The problem was solved through a call with a slight delay:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.callMethod()
}
The problem for me is that I was presenting two modals and I have to dismiss both and then execute some code in the parent window ... and then I have this error... I solved it seting this code in the dismiss o the last modal presented:
self.dismiss(animated: true, completion: {
self.delegate?.callingDelegate()
})
in other words instead of just dismiss two times .. in the completion block of the first dismiss call delegate that will execute the second dismiss.
What worked for me was to add the presentation of the alert to the main thread.
DispatchQueue.main.async {
self.present(alert, animated: true)
}
The presentation of the current viewController was not complete. By adding the alert to the main thread it can wait for the viewController's presentation to complete before attempting to present.
I got the same issue when i tried to present a VC which called inside the SideMenu(jonkykong).
first i tried inside the SideMenu and i called it from the delegate to the MainVC both had the same issue.
Solution: dismiss the SideMenu first and present the new VC after will works perfectly!.
This happened with me on our project. I was presenting our log in/log out ViewController as a pop-over. But whenever I tried to log back out again and display the pop-over again, I was getting this logged out in my console:
Warning: Attempt to present UIViewController on <MY_HOME_VIEW_CONTROLLER> which is already presenting (null)
My guess is that the pop-over was still being held by my ViewController even though it was not visible.
However you are attempting to display the new ViewController, the following code I used to solve the issue should work for you:
func showLoginForm() {
// Dismiss the Old
if let presented = self.presentedViewController {
presented.removeFromParentViewController()
}
// Present the New
let storyboard = UIStoryboard(name: "MPTLogin", bundle: Bundle(for: MPTLogin.self))
let loginVC = storyboard.instantiateViewController(withIdentifier: "LogInViewController") as? MPTLogInViewController
let loginNav = MPTLoginNav(rootViewController: loginVC!)
loginNav.modalPresentationStyle = .pageSheet;
self.present(loginNav, animated: true, completion: nil)
}
I faced the same kind of problem
What I did is from Interface builder selected my segue
Its kind was "Present Modally"
and its presentation was "Over current context"
i changed the presentation to "Default", and then it worked for me.
In my case I was trying to present a UIAlertController at some point in the app's lifetime after using a UISearchController in the same UINavigationController.
I wasn't using the UISearchController correctly and forgot to set searchController.isActive = false before dismissing. Later on in the app I tried to present the alert but the search controller, though not visible at the time, was still controlling the presentation context.
My problem was that (in my coordinator) i had presented a VC on a VC and then when i wanted to present the next VC(third one), presented the third VC from the first one which obviously makes the problem which is already presenting.
make sure you are presenting the third one from the second VC.
secondVC.present(thirdVC, animated: true, completion: nil)
Building on Mehrdad's answer: I had to first check if the search controller is active (if the user is currently searching):
if self.searchController.isActive {
self.searchController.present(alert, animated: true, completion: nil)
} else {
self.present(alert, animated: true, completion: nil)
}
where alert is the view controller to present modally.
This is what finally worked for me, as my project didn't exactly have a NavigationVC but instead, individual detached VC's. as xib files
This code produced the bug:
present(alertVC, animated: true, completion: nil)
This code fixed the bug:
if presentedViewController == nil{
navigationController?.present(alertVC, animated: true, completion: nil)
}
For me it was an alert that was interfering with the new VC that I was about to present.
So I moved the new VC present code into the OK part of my alert, Like this :
func showSuccessfullSignupAndGoToMainView(){
let alert = UIAlertController(title: "Alert", message: "Sign up was successfull.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
switch action.style{
case .default:
// Goto Main Page to show businesses
let mainStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc : MainViewController = mainStoryboard.instantiateViewController(withIdentifier: "MainViewController") as! MainViewController
self.present(vc, animated: false, completion: nil)
case .cancel:
print("cancel")
case .destructive:
print("destructive")
}}))
self.present(alert, animated: true, completion: nil)
}
My issue was that I was trying to present an alert from a view that wasn't on top. Make sure you present from the top-most viewController.
In my case this was an issue of a button which was duplicated in Interface Builder. The original button had a touch-up handler attached, which also presented a modal view. When I then attached a touch-up handler on the copied button, I forgot to remove the copied handler from the original, causing both handlers to be fired and thus creating the warning.
More than likely you have your Search button wired directly to the other view controller with a segue and you are calling performSegueWithIdentifier. So you are opening it twice, which generates the error that tells you "is already presenting."
So don't call performSegueWithIdentifier, and that should do the trick.
Make sure you Dismiss previous one before presenting new one!