I have a custom action sheet viewController. Which is presented modally on the current top view controller. Like this:
//MARK: Static Func
static func initViewController() -> CustomActionSheetViewController {
let customActionSheetViewController = CustomActionSheetViewController(nibName: "CustomActionSheetViewController", bundle: nil)
return customActionSheetViewController
}
func presentViewController<T: UIViewController>(viewController: T) {
DispatchQueue.main.async {
if let topViewController = UIApplication.getTopViewController() {
viewController.modalTransitionStyle = .crossDissolve
viewController.modalPresentationStyle = .overCurrentContext
topViewController.present(viewController, animated: true, completion: nil)
}
}
}
// MARK: UIApplication extensions
extension UIApplication {
class func getTopViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return getTopViewController(base: nav.visibleViewController)
} else if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
return getTopViewController(base: selected)
} else if let presented = base?.presentedViewController {
return getTopViewController(base: presented)
}
return base
}
}
And I am dismissing it like this:
#objc func dismissViewController() {
DispatchQueue.main.async {
if let topViewController = UIApplication.getTopViewController() {
topViewController.dismiss(animated: true, completion: nil)
}
NotificationCenter.default.removeObserver(self)
}
}
It's working perfectly fine. I have added the notification observer in my customTabbarController, to dismiss the action sheet if user tap on some another tabbar button like this:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
// print("Selected view controller", viewController)
// print("index", tabBarController.selectedIndex )
let tabbarNotiKey = Notification.Name(rawValue: "TabbarNotiKey")
NotificationCenter.default.post(name: tabbarNotiKey, object: nil, userInfo: nil)
}
The action sheet is right now presenting on Home tab > Profile (by push) > Action sheet (by modal). So if I tap on Home tab again it will dismiss the action sheet viewController and come back to Home perfectly. But if I tap on some other tabbar button rather than home and come back home, it shows a black screen. What I am missing here? Any suggestions would be highly appreciable.
I guess your code calls dismissViewController() twice.
When you press other tab, it removes the action sheet
And then you click home tab and it calls dismissViewController again, and now it removes the homescreenVC
Related
I'm looking for a way to dismiss all presented view controllers, and THEN present a view controller.
In my app, there's a main page, and the user can then click on a button that takes them to another page, and then they can click a button to submit some information. After they click to submit the evidence, I want to close all of the view controllers (so they get to the main page), and then I want to present a "Congratulations" screen. Ideally, this would be what I want to do:
self.view.window?.rootViewController?.dismiss(animated: true, completion: {
let congratsPopup = K.mainStoryBoard.instantiateViewController(withIdentifier: "congratsController") as! CongratsController
self.view.window?.rootViewController!.present(congratsPopup, animated:true, completion:nil)
})
Any ideas?
Cheers,
Josh
You can dismiss all viewcontrollers with below code block. In the completion block you can get the topViewController and you can present new viewController over topViewController. I also wrote down an extension for get the topViewController on the window.
UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: true, completion: { [weak self] in
// Get Top Controller With Extension
let topController = UIApplication.topViewController()
// Pressent New Controller over top controller
let congratsPopup = K.mainStoryBoard.instantiateViewController(withIdentifier: "congratsController") as! CongratsController
topController?.present(congratsPopup, animated: true, completion: nil)
})
Get Top View Controller Extension
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
}
}
How would I dismiss a modal View Controller and also its parent that was pushed?
self.presentingViewController?.dismiss(animated: true, completion: {
self.parent?.navigationController?.popViewController(animated: true)
})
This only dismisses the top modal.
You can go another way.
First, you have UINavigationController in your Home view. So you can write an extension that will allow you to go to the controller, which is in the navigation stack.
I tried making an implementation like this:
extension UINavigationController {
func routingPath(for controller: UIViewController) -> [UIViewController] {
guard viewControllers.contains(controller) else {
return []
}
var result: [UIViewController] = []
for previousController in viewControllers {
result.append(previousController)
if controller === previousController {
break
}
}
return result
}
func performNavigation(toPrevious controller: UIViewController,
shouldDismissModals: Bool = true) {
let previousViewControllers = routingPath(for: controller)
guard !previousViewControllers.isEmpty else { return }
viewControllers = previousViewControllers
if shouldDismissModals, let _ = controller.presentedViewController {
controller.dismiss(animated: true, completion: nil)
}
}
}
Then you can make a special method for UIViewController:
extension UIViewController {
func returnBackIfPossible(to controller: UIViewController? = nil,
shouldDismissModals: Bool = true) {
navigationController?.performNavigation(toPrevious: controller ?? self,
shouldDismissModals: shouldDismissModals)
}
}
Then you need to pass a reference for a Home controller to all of the next controllers (or store it somewhere). Next, when needed, you can call to homeViewController?.returnBackIfPossible() method, which will close all modals and reset navigation stack.
What is non-modal parent exectly?
Is it a view controller pushed by the navigation controller?
If then, you must pop that view controller from navigation controller.
My scenario, I am having Tabbar with three viewcontroller. Here, tabbar first viewcontroller I am showing tableview. If I click the tableview cell it will show one popup present model viewcontroller. In this present popup viewcontroller I am maintaining two bar button cancel and done. If I click done It will dismiss and show tabbar main view controller. While dismiss time I need to pass some values with button flag from present popup view controller to tabbar main viewcontroller.
Here, below my dismiss popup pass viewcontroller code (VC 2)
#IBAction func apply_click(_ sender: Any) {
print("Dimiss Filter")
dismiss(animated: true, completion: {
if let navView = self.tabBar?.viewControllers?[0] as? UINavigationController {
if let secondTab = navView.viewControllers[0] as? HomeViewController {
secondTab.selectedIndexFromFirstTab = self.selectedIndex
//secondTab.item = self.item
secondTab.tfData = "YES"
}
}
self.tabBar?.selectedIndex = 0
})
}
Here, Tabbar main view controller code (receiving values) (VC 1)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("SELECTED INDEX:\(selectedIndexFromFirstTab)")
print("RESPONSE:\(tfData)")
}
I am not receiving values, how to solve this issue.
From my previous answer you need to make few changes to existing code if you want to pass values from your child view to main tab bar controller.
For that first of all you need to declare a method into your main TabBarViewController
func tabPressedWithIndex(index: Int, valueToPass: String) {
print("selected Index: \(index)")
print("Value from user: \(valueToPass)")
}
Now you need to pass that tab bar controller to your detail view with didSelectRowAt method and it will look like:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
vc.selectedIndex = indexPath.row
vc.tabBar = self.tabBarController as? TabBarViewController // Here you need to assign tab bar controller.
self.present(vc, animated: true, completion: nil)
}
Now next thing is when you click on dismiss button from detail view controller you need to add one line below self.tabBar?.selectedIndex = 1:
self.tabBar?.tabPressedWithIndex(index: 1, valueToPass: self.userTF.text!)
Now this will pass the values to main tab bar controller and method tabPressedWithIndex will call and print your data in your main tab.
Check out demo project for more info.
You can achieve it multiple ways. Using blocks/closures, protocols or if you are using RxSwift than using controlled property or using controlled events. Because I can't demonstrate everything here am gonna write protocol
Using Protocol
Step 1:
Declare a protocol in your modal view controller
#objc protocol ModalViewControllerProtocol {
func dismiss(with data: String)
}
Step 2:
ViewController that presents this ModalViewController make it to confirm the protocol
extension HomeViewController: ModalViewControllerProtocol {
func dismiss(with data: String) {
//use the data here
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
}
Step 3:
Declare a variable to hold delegate reference in ModalViewController
weak var delegate: ModalViewControllerProtocol? = nil
Step 4:
In your ViewCOntroller that presents the modalViewController pass self as a delegate to ModalViewController before presenting
let modalVC = //...
modalVC.delegate = self
self.present(modalVC, animated: true, completion: nil)
Finally in IBAction of ModalViewController simply call
#IBAction func apply_click(_ sender: Any) {
self.delegate?.dismiss(with: "your_data_here")
}
Using Block/Closure
Step 1:
In your modal ViewController declare a property that accepts a block/closure
var completionBlock: (((String) -> ()))? = nil
Step 2:
In your ViewController that presents this ModalViewController, pass a block before presenting it
let modalVC = //...
modalVC.completionBlock = {(data) in
debugPrint(data)
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
self.present(modalVC, animated: true, completion: nil)
Step 3:
Finally in your ModalViewController IBAction simply execute the block passed
if let block = completionBlock {
block("your data here")
}
Hope it helps
This is the solution
self.dismiss(animated: true) {
if let tabController = self.presentingViewController as? UITabBarController {
if let navController = tabController.selectedViewController as? UINavigationController {
if let secondTab = navController.viewControllers.first as? HomeViewController {
secondTab.tfData = "YES"
}
} else {
if let secondTab = tabController.selectedViewController as? HomeViewController {
secondTab.tfData = "YES"
}
}
}
}
I have a view controller embedded inside of a navigation controller. This navigation controller is the third item inside of my tab bar controller. I want to present the view controller modally.
This is what I've tried, but it does not run
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
// CameraView is the Storyboard ID of the VC I want to present
if viewController is EditPreviewVideosViewController {
if let newVC = tabBarController.storyboard?.instantiateViewController(withIdentifier: "CameraView") {
// None of this prints
print()
print("new vc is allowed")
print()
tabBarController.navigationController?.present(newVC, animated: true, completion: {
print("complete")
})
return false
}
}
return true
}
Get the right navigation controller from tabbarController childs
if let nav = tabBarController.viewControllers[tabBarController.selectedIndex] as UINavigationController {
nav.present(newVC, animated: true, completion: {
print("complete")
})
}
You are finding view controller in wrong storyboard
with this line
let newVC = tabBarController.storyboard?.instantiateViewController(withIdentifier: ....
It should be
let newVC = UIStoryboard.init(name: "NameOfYourStoryboardContainsVideo", bundle: nil).instantiateViewController(withIdentifier: "CameraView")
Now your if will execute correctly
I've got a timer showing an alert when finished.
This alert view should be presented in the view controller which the user is currently in.
My feeling is this could be accomplished much more effective than the following:
The way I'm doing this now is give an observer for a notification to each of my 5 view controllers as well as a method to create and present that alert.
Is there a way to only set up the alert once and then present it in the view controller that is currently active?
Here's my code:
// I've got the following in each of my view controllers.
// In viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(SonglistViewController.presentSleepTimerFinishedAlert(_:)), name: "presentSleepTimerFinishedAlert", object: nil)
}
func presentTimerFinishedAlert(notification: NSNotification) {
let alertController = UIAlertController(title: "Timer finished", message: nil, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
presentViewController(alertController, animated: true, completion: nil)
}
Thanks a lot for any ideas!
extension UIApplication {
/// The top most view controller
static var topMostViewController: UIViewController? {
return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
}
}
extension UIViewController {
/// The visible view controller from a given view controller
var visibleViewController: UIViewController? {
if let navigationController = self as? UINavigationController {
return navigationController.topViewController?.visibleViewController
} else if let tabBarController = self as? UITabBarController {
return tabBarController.selectedViewController?.visibleViewController
} else if let presentedViewController = presentedViewController {
return presentedViewController.visibleViewController
} else {
return self
}
}
}
With this you can easily present your alert like so
UIApplication.topMostViewController?.present(alert, animated: true, completion: nil)
One thing to note is that if there's a UIAlertController currently being displayed, UIApplication.topMostViewController will return a UIAlertController. Presenting on top of a UIAlertController has weird behavior and should be avoided. As such, you should either manually check that !(UIApplication.topMostViewController is UIAlertController) before presenting, or add an else if case to return nil if self is UIAlertController
extension UIViewController {
/// The visible view controller from a given view controller
var visibleViewController: UIViewController? {
if let navigationController = self as? UINavigationController {
return navigationController.topViewController?.visibleViewController
} else if let tabBarController = self as? UITabBarController {
return tabBarController.selectedViewController?.visibleViewController
} else if let presentedViewController = presentedViewController {
return presentedViewController.visibleViewController
} else if self is UIAlertController {
return nil
} else {
return self
}
}
}
You can find the Top ViewController on the navigation stack and directly present the AlertController from there. You can use the extension method posted here to find the Top ViewController from anywhere in your application:
https://stackoverflow.com/a/30858591/2754727
It really depends on your navigation schema.
First of all you will need current VC. If you've got root view controller as navigation controller and don't show any modals you can get current VC from rootVC. If you've got mixed navigation. i.e. tabbar and then navigation controllers inside, with possible some modals form them you can write an extension on AppDelegate which will search and return current VC.
Now you should pin somewhere this timer class - it may be a singleton or just be pinned somewhere. Than in this timer class, when the timer ends you can look for current VC (using AppDelegate's extension method or referring to your root navigation controller) an present an alert on it.