UITabBarController's viewControllers present modal controller issue - ios

I have a UITabBarController with 4 viewControllers setup.
One of the controller has a button that present another controller (wrapped on UINavigationController) with the following setup:
self.definesPresentationContext = true
navController.modalPresentationStyle = .overCurrentContext
navController.modalTransitionStyle = .crossDissolve
self.present(navController, animated: true)
Until this point is working fine.
Now if I switch to another tab (while the previous modal is open), and return again to the tab that presented the modal (The screen is still there, that's ok). Then if i close the modal (from a Button), the modal is dismissed but the controller view's has gone (white), then if I switch to another tab and return to the tab again, the view load correctly.
Note: For this case, I need overCurrentContext, don't want to block UITabBarController (with fullScreen).. Also try with .currentContext, custom

If this is the same bug that I demonstrate here, the workaround I give is to prevent the user from switching to another tab while this tab is showing the presented view controller:
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.delegate = self
}
extension FirstViewController : UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
return self.presentedViewController == nil
}
}

Related

Present a Tab Bar View Modally when View Controller is embedded in a Navigation Controller?

My storyboard is arranged as so.
Red: Tab Bar Controller that segues to...
Orange: Nav Controllers that have embedded...
Green: View Controllers
I want to make my Middle Tab View (green) present itself modally, sort of like how the reddit app does it with its middle 'Post to Reddit' button. When this Middle View is dismissed the original Tab that was open beforehand will be returned to. How can this be done?
One way to do this...
Give the UINavigationController that is your 2nd tab a StoryboardID - such as "createItemsNavController" - then implement shouldSelect in your custom tab bar controller class.
If the 2nd tab is selected (tabs, like all arrays, are Zero based), instantiate your "createItemsNavController" and present it, returning false for shouldSelect:
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let indexOfTab = viewControllers?.firstIndex(of: viewController) else {
return true
}
if indexOfTab == 1 {
if let vc = storyboard?.instantiateViewController(withIdentifier: "createItemsNavController") as? UINavigationController {
present(vc, animated: true, completion: nil)
}
return false
}
return true
}
If you're going that route, you could also (and it might be a good idea to) replace that Tab connection in your Storyboard with a blank view controller... as you probably want to avoid having it loaded by the tab bar controller, even if you never allow that tab to be activated.
As a side note: that could be a very confusing UX. Users (and Apple) like apps that conform to common interface actions. Since users are familiar with tab bars, where selecting a tab button switches to that tab, changing the functionality in this way may be frowned upon.
Of course, it's your app, and your design choice...

iOS Black screen when close modally presented view controller

I have a tabbar controller and suppose if the user goes to 3rd tab and in 3rd view controller, User presses a button and there I present the view controller as modal .
Now if user switched the tab let say tab 1 or 2 without closing the modally presented view controller, and mean while he comes back to tab 3, the user will still able to see the modally presented view controller, now if user closes the modally presented view controller here, then there is a black screen instead of view controller that opened the modally presented view controller.
The possible work around is that I hide the tabbar controller so that user must close modally presented the view controller first. but it is not the requirement right now. Due to some reasons I can not hide bottom tab bar controller.
Please suggest me how to do following
First of all when user switches tab after opening modally presented view controller, so when he closes the modally presented vc he must see the screen at the back
If point no 1 is not possible so when user goes back again to tab 3 (in which he started modally presented VC) there should not be modally presented view controller. In other words when user switched the tab and the modally presented view controller is opened, it must close, before moving to other tab.
You can do it with UITabBarControllerDelegate.
Option 1: Creating subclass of UITabBarController
class MyTabBar: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if let currentlySelectedViewController = self.viewControllers?[self.selectedIndex] as? YourThirdTabViewController,
let presentedViewController = currentlySelectedViewController.presentedViewController {
presentedViewController.dismiss(animated: false, completion: nil)
}
return true
}
}
Here I am assuming that I have my own sub class of UITabBarController which is set as UITabBarControllerDelegate using self.delegate = self in view didLoad.
Option 2: Make TabBarController's ViewController UITabBarControllerDelegate in their ViewDidLoad
If you dont wanna have your own subclass of UITabBarController all you have to do is simply set your self as UITabBarControllerDelegate in each ViewController's ViewDidLoad
You can have a common protocol or extension to UIViewController and confirm UITabBarControllerDelegate to avoid code duplication if you decide to go down the path of not subclassing UITabBarController
protocol TabBarControllerProtocol: UITabBarControllerDelegate where Self: UIViewController {}
extension TabBarControllerProtocol {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if let currentlySelectedViewController = self.tabBarController?.viewControllers?[self.tabBarController?.selectedIndex ?? 0] as? SomeViewController,
let presentedViewController = currentlySelectedViewController.presentedViewController {
presentedViewController.dismiss(animated: false, completion: nil)
}
return true
}
}
In whichever ViewController, you want this logic to kick in you can say
class SomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.delegate = self
}
}
extension SomeViewController: TabBarControllerProtocol {}
Thats it.
shouldSelect viewController gets called every time user taps on tab to switch.
The if condition checks if ViewController at selectedIndex (already selected not the new one) is viewController of specific interest or not and also checks if this view controller has presented anything or not, if it has presented it will call dismiss on it
This way when user switches tab from 3rd tab to any other tab, if third tab VC has presented another view controller modally it will dismiss it before it switches tab.

Present ViewController modally from UITabBarController without hiding the tab bar

I have a UITabBarController with 5 items in it. I have also a side menu with couple of items. When tapping one of the items in the side menu I need to present view controller modally without hiding the tab bar. I tried the following:
Presenting it from the selectedViewController
tabBarController.selectedViewController?.present(contactsViewController, animated: false, completion: {})
Presenting it from the UITabBarController itself
tabBarController.present(contactsViewController, animated: true) {}
Adding a modal segue in Storyboard from the selectedViewController to the contactsViewController and performing it
All of these led to the tabBar being hidden. Is it possible to present the view controller modally without hiding the tab bar and how?
You can use the UITabBarControllerDelegate methods to present menu modally.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool
{
if tabBarVC?.tabBar.selectedItem?.tag == 5
{
tabBarVC?.present(SideMenuManager.default.menuRightNavigationController!, animated: true, completion: nil)
return false
}
return true
}

How I could clean UINavigationBar transitions history?

I currently have parental "menu" TableView with UINavigationBar and from each cell there is a segues by reference outlet to 3 similar Views with different information.
In each View there is a buttons to other 2 Views.
With every button's segue opens another View.
The problem:
From every View UINavigationBar's back button returns me to previous View but i tries to make back button to "menu".
Additional Bar Button Item and segue from it makes very close effect but segue animation is not like in UINavigationController.
How I could clean UINavigationBar transitions history in segue to initial View?
You can try pop to root view controller or You can edit navigation controller viewControllers property and remove/add some VC in between.
You can try Unwind Segue mechanism too.
Here are some methods(function) that navigation controller providing for pop operations. They are returning optional UIViewController (intance) from it’s navigation stack, that is popped.
open func popViewController(animated: Bool) -> UIViewController? // Returns the popped controller.
open func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? // Pops view controllers until the one specified is on top. Returns the popped controllers.
open func popToRootViewController(animated: Bool) -> [UIViewController]?
Here is sample code as a solution to your query::
// if you want to back to root of your app
if let rootNavigationController = self.window?.rootViewController as? UINavigationController {
rootNavigationController.popToRootViewControllerAnimated(true)
}
// But if you want to back to root of your current navigation
if let viewcontroller = self.storyboard?.instantiateViewController(withIdentifier: "NewViewController") as? NewViewController { // or instantiate view controller using any other method
viewcontroller.navigationController?.popToRootViewControllerAnimated(true)
}

Dismiss View Controller inside UITabController

I've a project which contains a tab controller, as the main 'page'.
I want to add a UITabBar button item, which presents a view controller modaly, and within that view controller, add a dismiss button that dismisses that view controller and returns to the previous tab selection.
To clarify, it's something that the Medium app for iOS uses, when you click on the create post item, it presents it modaly and dismisses it when you want to.
I can present the view controller, but I can't dismiss it.
Hope I made myself understandable.
Example:
So I've managed to solve this as I wanted to, hope the answer will help someone in the future.
First of all, I set on my first loaded view controller, lets say my 'Home' view controller the tabBarController delegate to be my AppDelegate.swift file (this is personal preference), as:
self.tabBarController?.delegate = UIApplication.shared.delegate as? UITabBarControllerDelegate
Then in my AppDelegate.swift file i added to the class properties the delegate UITabBarControllerDelegate so I could access the tabBarController delegate functions, such as
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {}
Then edited the function to interact with my specific view controller.
The final version of the function:
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController is ViewControllerToPresentModaly {
if let newVC = tabBarController.storyboard?.instantiateViewController(withIdentifier: "modalVC") {
tabBarController.present(newVC, animated: true)
return false
}
}
return true
}
Here if you want the regular tab behaviour, return true or your own return false
;)

Resources