swift get topmostViewController avoid UIAlertController - swift

i want to get topMostVC so i did this function:
func 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
}
Then called using:
AppDelegate.shared.window?.rootViewController?.topMostViewController()
But i have problem is when UIAlertController is showing in screen, it's return UIAlertController, not my expect viewcontroller
Can anyone tech me how to avoid UIAlertController, thanks

Just add one more condition
if let presented = self.presentedViewController,
!(presented is UIAlertController) { // <== Here or use !presented.isKind(of: UIAlertController.self)
return presented.topMostViewController()
}

Related

Shortcut Items - Perform Segue

I'm trying to add a shortcut item to my app. I have the item appearing, and it's responding but can't work out how to get the segue to work. I have it going to the right tab but need to go to the root view and perform a segue from there.
The segue is already setup on the ProjectList view controller and its called "addProject"
My view storyboard is setup as so:
UITabViewController -> UINavigationController-> UITableViewController (ProjectList) -> Other additional views
func applicationDidBecomeActive(_ application: UIApplication) {
if let shortcutItem = shortcutItemToProcess {
if shortcutItem.type == "addProject" {
if let window = self.window, let tabBar : UITabBarController = window.rootViewController as? UITabBarController {
tabBar.selectedIndex = 0
}
}
shortcutItemToProcess = nil
}
}
Solved with
func applicationDidBecomeActive(_ application: UIApplication) {
if let shortcutItem = shortcutItemToProcess {
if shortcutItem.type == "addProject" {
guard let window = self.window else { return }
guard let tabBar = window.rootViewController as? UITabBarController else { return }
guard let navCon = tabBar.viewControllers?[0] as? UINavigationController else { return }
guard let projectList = navCon.rootViewController as? ProjectList else { return }
projectList.performSegue(withIdentifier: "addProject", sender: nil)
tabBar.selectedIndex = 0
}
shortcutItemToProcess = nil
}
}

How to check after popping a ViewController, the ViewController on top of the stack, is a particular one?

Suppose I am currently on BaseViewController . I can approach this view controller class from 2 other view controllers, say A and B. So when I pop BaseViewController how do I check if A or B is present on top of the stack? Here is my code below(inside BaseViewController):
func goToAOrBViewController {
// If after popping viewController is A do this
navigationController.popViewController(animated: true)
// Id after popping viewController is B do this
// Instantiate B and push it
}
There are many methods to do this
First and easiest method
func visibleViewController() -> UIViewController? {
let appDelegate = UIApplication.shared.delegate
if let window = appDelegate!.window {
return window?.visibleViewController
}
return nil
}
func goToAOrBViewController {
// If after popping viewController is A do this
navigationController.popViewController(animated: true)
if visibleViewController() == viewControllerA{
//Do stuff for A
}else{
//Do stuff for B
}
}
Second and obvious method
let topController = UIApplication.sharedApplication().keyWindow?.rootViewController
if topController == viewControllerA {
//Do stuff for A
}else{
//Do stuff for B
}
You can pop and push view controller at the same time.
not tested:
navigationController?.viewControllers.removeLast()
let topVC = navigationController?.viewControllers.last
if topVC == A {
navigationController?.viewControllers.append(A)
navigationController?.setViewControllers(A, animated: false)
} else {
navigationController?.viewControllers.append(B)
navigationController?.setViewControllers(B, animated: false)
}
Inside BaseViewController class:
func goToAOrBViewController() {
guard let navigationController = self.navigationController else { return }
navigationController.popViewController(animated: false)
guard let viewController = navigationController.topViewController else { return }
if viewController is ViewControllerA {
// Do something for A
} else if viewController is ViewControllerB {
// Do Something for B
}
// You need not put else-if case if you are sure BaseVC is reachable from A and B only (just use else)
}

Swift - Present UIAlertController in extension function

I've wrote a label extension which detect links in the label.
It's attributed text with NSLinkAttributeName for links and I'm using
func handleTapOnLabel(_ tapGesture:UITapGestureRecognizer){} to detect where is the tap and which link to choose to open. The problem is that I use the function in extension for UILabel and I want to present an UIAlertController to ask the person - "You are about to open this link in Safari. Would you like to proceed?"... So I can't access viewcontroller to use the function present(UIAlertController... to display the alert. Any suggestions how this can be happened in extension? How to access label's viewcontroller directly from extension ?
Method 1) Present UIAlertController on top most ViewController in ViewController's hierarchy
extension UILabel { //your extension
func openLinkAction() { //your extension custom method
//code
if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
}
// topController should now be your topmost view controller
// present alert on this controller
}
//and present here with vcInstance
}
}
Method 2) Pass ViewController's object as parameter
extension UILabel { //your extension
func openLink(vc: UIViewController) { //your extension custom method function
// code
// present alert on this vc
}
}
// For getting top most controller you ca use..
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
}
}
// topController
if let topController = UIApplication.topViewController() {
}

Back when current view controller is not presented by segue. Swift

I am trying to present VC2 from VC1 without using segue. It works. Then, I tried to use self.navigationController?.popViewControllerAnimated(true) to back but it does not work. I am wondering what is the code I should use to back from VC2 to VC1. Below code is in appDelegate.
AppDelegate
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let VC2 = storyboard.instantiateViewControllerWithIdentifier("VC2") as! VC2
let navController = UINavigationController(rootViewController: VC2)
self.topViewController()!.presentViewController(navController, animated: false, completion: nil)
func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
if let MMDrawers = base as? MMDrawerController {
for MMDrawer in MMDrawers.childViewControllers {
return topViewController(MMDrawer)
}
}
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
return base
}
VC2
#IBAction func backButtonTapped(sender: AnyObject) {
print(self.navigationController?.viewControllers) // print([<MyAppName.VC2: 0x12f147200>])
self.navigationController?.popViewControllerAnimated(true)
}
Here is the Flow of your navigation :
Current Screen - Presenting A New Screen (Which itself embed within a navigation controller with vc2) so Popviewcontroller won't work .
If you present any viewcontroller then popviewcontroller wont work rather use dismissviewcontroller to come out previous screen .
Use This :
self.dismissViewControllerAnimated(true, completion: {})
Solved!
Thanks.

Get the top ViewController in iOS Swift

I want to implement a separate ErrorHandler class, which displays error messages on certain events. The behavior of this class should be called from different other classes.
When an error occurs, it will have an UIAlertView as output.
The display of this AlertView should ALWAYS be on top. So no matter from where the Error gets thrown, the topmost viewController should display the AlertMessage (e.g. when an asynchronous background process fails, I want an error message, no matter what View is displayed in the foreground).
I have found several gists which seem to solve my problem (see the code below).
But calling UIApplication.sharedApplication().keyWindow?.visibleViewController() does return a nil-value.
Extension from gist
extension UIWindow {
func visibleViewController() -> UIViewController? {
if let rootViewController: UIViewController = self.rootViewController {
return UIWindow.getVisibleViewControllerFrom(rootViewController)
}
return nil
}
class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
if vc.isKindOfClass(UINavigationController.self) {
let navigationController = vc as! UINavigationController
return UIWindow.getVisibleViewControllerFrom( navigationController.visibleViewController)
} else if vc.isKindOfClass(UITabBarController.self) {
let tabBarController = vc as! UITabBarController
return UIWindow.getVisibleViewControllerFrom(tabBarController.selectedViewController!)
} else {
if let presentedViewController = vc.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
} else {
return vc;
}
}
}
}
Amit89 brought a way to a solution up. You have to call the .windowproperty of the AppDelegate.
So I changed the Swift code from the link below to work as intended to find the topmost ViewController. Make sure, that the view is already in the view hierarchy. So this method cannot be called from a .viewDidLoad
Extension to find the topmost ViewController*
extension UIApplication {
class func topViewController(base: UIViewController? = (UIApplication.sharedApplication().delegate as! AppDelegate).window?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
This code originated from GitHub user Yonat in a comment to an objectiveC equivalent. I only changed the bits of code to get it to work without the .keyWindow property
Did you try this from the same link?
let appDelegate = UIApplication.sharedApplication().delegate as! MYAppDelegate//Your app delegate class name.
extension UIApplication {
class func topViewController(base: UIViewController? = appDelegate.window!.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}