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.
Related
Iv been searching and reading many SO articles and just can't seem to get this to work.
I have a view controller called Startup which does a few web requests etc then takes the user to the login screen.
upon logging on I'm using this code to try and open the content view controller called Home after dismissing the login view controller.
self.dismiss(animated: true, completion: {
let mainStoryboard: UIStoryboard = UIStoryboard(name:"Main",bundle:Bundle.main)
let secondViewController: Home = mainStoryboard.instantiateViewController(withIdentifier: "home") as! Home
self.presentingViewController?.present(secondViewController, animated: true, completion: nil)
})
my problem is that the Login VC dismisses fine but doesn't open the Home VC because self.presentingViewController is nil.
Any help would be appreciated,
The answer already given is fine; I just want to expand on the nature of the problem here.
The issue is that we have a login screen that we want to use just once. When it has been passed through, we want to proceed to a base screen, and from now on, when the app launches, we will always start at the base screen.
The problem of launching from the login screen or the base screen, I leave to one side. Let's just talk about how the login screen and the base screen should relate, when the login screen is used. I can think of three approaches:
Present the base screen from the login screen without dismissing the login screen. I know it sounds nutty, but why not? Just present the base screen and give the user no way to go back to the login screen. The base screen now covers the whole interface and we can proceed to normal use of the app.'
Rip out the whole interface and replace it. A lot of people do this: you just change the window's root view controller to the base screen. I dislike this, however.
Start with a parent view controller that the user never actually interacts with. The login screen is the child of that parent. Replace the login screen with the base screen, using any desired transition. That is the solution I actually prefer.
You need to get topViewController when you dismissed the viewController.
For getting topViewController you can use extension below.
extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}}
Then you can present new viewController over topViewController
self.dismiss(animated: true, completion: {
let mainStoryboard: UIStoryboard = UIStoryboard(name:"Main",bundle:Bundle.main)
let secondViewController: Home = mainStoryboard.instantiateViewController(withIdentifier: "home") as! Home
let topVC = UIApplication.topViewController()
topVC?.present(secondViewController, animated: true, completion: nil)
})
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 an app that has 4 tab bar items (A, B, C, D). I'm using notification data to direct a user to a child vc of A ('A-child') and I want to populate the search bar in A-child with a value from the notification.
I'm successfully getting the notification and value, that's fine. I'm also able to navigate to A just fine using:
let tabBarController = UIApplication.shared.keyWindow?.rootViewController as! UITabBarController
tabBarController.selectedIndex = 0
But I then need to navigate to A-Child VC from A and then set the search bar text (I'm less worried about the search bar text piece as I think I can work that out once I get the right VC stack in place). I could of course use a segue to go straight from where the user when they tap on the notification to A-child, but then I lose the expected navigation behaviour for the user from A-child.
I know I'm not the first to ask a question like this, and I've gone through everything I can find on SO relating to this - but can't make any of the answers click. Help is much appreciated!
Edit:
I've got it partially working with this:
if let tabbarController = UIApplication.shared.keyWindow?.rootViewController as?
UITabBarController {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "AChildViewController") as!
AChildViewController
vc.searchString = "text"
tabarController.present(vc, animated: true, completion: nil)
}
Not sure if this is an appropriate way or I'm going to get myself in trouble
It isn't being presented inside the navigation controller - so I'm not getting the top nav bar (including the critical search bar)
It isn't being presented inside the tab bar controller
when I tried to do tabBarController.navigationController? < the navigation controller is nil
EDIT 2 - Solution:
Found an unaccepted answer from a couple of years ago that did the trick for me via: https://stackoverflow.com/a/51763243/12481584
let tabBar: UITabBarController // get your tab bar
tabBar.selectedIndex = 0 // eg. zero. To be sure that you are on correct tab
if let navigation = tabBar.viewControllers?[tabBar.selectedIndex] as? UINavigationController {
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
if let chatViewController = storyboard.instantiateViewController(withIdentifier: "chatViewController") as? ChatViewController {
navigation.pushViewController(chatViewController, animated: true)
}
}
The question is not so clear, but I assume your problem is basically how to navigate to a child controller of TAB A from anywhere.
There are multiple ways to do this (deep-linking), but the most straight forward way literally just do your usual approach of pushing, popping, presenting, dismissing of controllers and combine local storage of your data that indicates where you should redirect the user to after tapping a push notification or deep-linking from anywhere such as a website.
An extension of getting the current or top most screen should help, for instance, the is how I do it:
import UIKit
var windowRootController: UIViewController? {
if #available(iOS 13.0, *) {
let windowScene = UIApplication.shared
.connectedScenes
.filter { $0.activationState == .foregroundActive }
.first
if let window = windowScene as? UIWindowScene {
return window.windows.last?.rootViewController
}
return UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController
} else {
return UIApplication.shared.keyWindow?.rootViewController
}
}
/// Category for any controller.
extension UIViewController {
/// Class function to get the current or top most screen.
class func current(controller: UIViewController? = windowRootController) -> UIViewController? {
guard let controller = controller else { return nil }
if let navigationController = controller as? UINavigationController {
return current(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return current(controller: selected)
}
}
if let presented = controller.presentedViewController {
return current(controller: presented)
}
return controller
}
}
Now, onto your specific problem. So assuming you really now handle the redirection to TAB A properly, the next thing you would do is push the Child A after going to the TAB A, and then in the Child A didAppear, put the text in the searchBar and do the searching.
I have app with 7 screens(VC) which are embedded in navigation controller.
And I have one separate VC that is not embedded. This VC is acting like custom popup class PopupView: UIViewController {} and get's called by pressing button from every screen that I have in my app using custom segue (setup as custom segue in storyboard):
open class MIBlurPopupSegue: UIStoryboardSegue {
open override func perform() {
MIBlurPopup.show(destination, on: source)
} }
In this popup there's a button that should open another VC (VC always the same) that is embedded in navigation stack.
What I'm trying to achieve is to actually open that VC that is inside navigation stack by pressing button in Popup VC and then get back to the screen where Popup was called.
So, user journey will look like - Opened VC1(2,3,5,6,7) -> Called popup VC -> Pressed button -> opened VC4 -> pressed navigation back button -> returned to VC1.
I what I have right now:
Connected in storyboard all 6 screens to VC4, with segues ids
Tried performSegue(withIdentifier: "toVC4"), present(vc, animated: true, completion: nil)
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "VC4")
self.present(controller, animated: true, completion: nil)
Using protocols to call func in VC1 but failed.
I'm definitely missing something and would be very thankful if someone could provide code sample to solve this issue.
You can try this inside the popup button's action
self.dismiss(animated:true) {
if let nav = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController {
let vc1 = storyboard!.instantiateViewController(withIdentifier: "MenuId")
let finalVC = storyboard!.instantiateViewController(withIdentifier: "finalId")
nav.setViewControllers([vc1,vc4],animated:true) // set it to false if you want
}
}
So i have an app were you're supposed to chose a location from a map but the way i'm doing it is by a popup off a view controller that has a MapKit and a search bar and a button for choosing users location, the problem lies on when i'm done with the View I normally called the
self.view.removeFromSuperview()
but the thing that is being remove is the View controller itself but il leaves behind the navigation bar and it doesn't let me do nothing else (in fact when I go to another tab and return to the same one the VC returns).
the way i'm instantiating the VC is like this:
func location ()
{
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = mainStoryboard.instantiateViewController(withIdentifier: "mapV") as! UBICACIONViewController
let nav1 = UINavigationController()
nav1.viewControllers = [vc]
self.addChildViewController(nav1)
self.view.addSubview(nav1.view)
vc.delegate = self //this for the info that i'm getting afte(works fine)
nav1.didMove(toParentViewController: self)
}
So until here works fin but when the user pick te button that says "Use My Location" and tries to return there's the problem.
in my VC view controller this is what happens when finished:
func Remove()
{
if let del = delegate {
del.dataChanged(str: Event.sharedInstance.Location!)
}
self.view.removeFromSuperview() //Problem Here :/
}
Second VC
Return to First VC
Actually you have added a child view controller nav1. And nav1 has a subview nav1.view.
while removing you are removing this view from its superview, not the child view controller added.
Any ways, From you screen short..I understand that the pop up view is off full screen then why dont you present this VC modally, or else push it to navigation stack.