1

first of all sorry if the title is a bit confusing, I'll try to explain it better.

I don't use StoryBoard in my application so everything is done programmatically.

So I have a UITabBarController which is the rootViewController of my application as defined in the AppDelegate :

 window?.rootViewController = CustomTabBarController()

As you can see, the customized TabBar is defined as follow in the class CustomTabBar :

class CustomTabBarController: UITabBarController {

override func viewDidLoad() {

    super.viewDidLoad()

    let mainController = MainViewController()
    let mainNavigationController = UINavigationController(rootViewController: mainController)
    mainNavigationController.title = "Home"
    mainNavigationController.tabBarItem.image = UIImage(named: "icon_home")

    let searchController = SearchViewController()
    let searchNavigationController = UINavigationController(rootViewController: searchController)
    searchNavigationController.title = "Search"
    searchNavigationController.tabBarItem.image = UIImage(named: "icon_search")

    // I have four items in the TabBar, all defined the same way as  above.
    //I removed them to keep the code a bit shorter.

    viewControllers = [mainNavigationController, cameraNavigationController, searchNavigationController, profileNavigationController]
      }
     }

Basically the TabBar looks as follow :

enter image description here

So when I click a TabItem, the NavigationController for the specific TabItem appears with its rootViewController ; everything works fine here.

However, the root View Controller for the Camera is full screen, meaning the TabBar is not visible for the user. I added a close button on the view so the user can "quit" the view. The problem is : as every item has its own navigation controller, when the camera item is selected, the CameraViewController is the first in the NavigationController's stack, therefore, I can't pop it if the user clicks the close button.

I managed to find a way around it with this answer.

//close button clicked
self.tabBarController.selectedIndex = 0;

It works but this way, the user is "redirected" every time to the same tab.

What I want to do is "redirect" the user to the previous tab he was in. For example, let's say he was in "Search" and then he clicked on the camera TabItem, if he decides to close, he will be back in the "Search" section.

A solution would be to have a variable that stores the actual ViewController's associated position so when the button close is clicked, I just set the selected index to this variable. However, I don't think this is very elegant.

Another one would be to only have three NavigationController (deleting the one for the Camera) and just pushing/poping the CameraViewController on the actual NavigationController (which changes depending on the section the user is). This solution seems better but I can't find a way to make it work. I've tried to implement the function didSelectViewController and check if it equals to the cameraViewController,(if true) just push it to the actual NavigationController. It doesn't work as the actual NavigationController is "nil" when I try to perform this action.

My question is : What is the best way to perform the task ?

Thanks for your time and sorry for the long post.

EDIT 2 :

So I tried the solution proposed by Joe Benton, which is similar to comments I received.

The idea is to present the camera screen modally with shouldSelectViewController, his code was in Objective-C so here is the Swift 2 version :

func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
    if viewController == cameraViewController {
        self.presentViewController(cameraViewController, animated: true, completion: nil)
        return false
    }
    return true
    }
 }

As you can see, I present directly the ViewController and not a NavigationController but this is a detail.

Don't forget to inherit from UITabBarControllerDelegate like so :

class CustomTabBarController: UITabBarController, UITabBarControllerDelegate 

And set the delegate to self like so :

self.delegate = self

However, this solution does not work, if you try this code and select the CameraTab, you will get the following error :

Application tried to present modally an active controller UITabBarController

After searching a bit online, I appears that you can't present modally a ViewController that is used by the TabBarController, meaning you can't use presentViewController for a viewController defined previously this way :

viewControllers = [mainNavigationController, cameraViewController, searchNavigationController, profileNavigationController]

You can learn more about it here

So what I did was to instantiate two CameraViewController at the top of the class (so I can access them wherever I want) this way :

class CustomTabBarController: UITabBarController, UITabBarControllerDelegate {

    let cameraControllerTest = CameraViewController()
    let cameraViewController = CameraViewController()

    override func viewDidLoad() {...}
    ...
}

Then, I added one of them in the ViewControllers array so it appears on the UITabBarController

I doesn't matter which one.

After that, in the shouldSelectViewController function, you just check which tab was clicked, if it is the instance of the CameraViewController you have in your TabBar array, you just present the other instance that is not in the array, else, you just return true. It looks like this :

func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
        if viewController == cameraViewControllerInArray {
            self.presentViewController(cameraViewControllerNotInArray, animated: true, completion: nil)
            return false
        }
        return true
        }

I don't know if it is the best way to get around this error so let me know. Thank you all for your answers !

Community
  • 1
  • 1
Anjou
  • 341
  • 2
  • 3
  • 16
  • 1
    Have you tried displaying the Camera controller modally, instead of in a nav controller? I have done this in a similar case. – Mike Taverne Apr 05 '16 at 05:47
  • 1
    As @MikeTaverne says, your camera button should present the camera view morally over the top of your tab bar controller. The you can simply dismiss the camera cc and you will be back exactly where you were – Paulw11 Apr 05 '16 at 06:46
  • Have you read my answer @Anjou – Joe Benton Apr 05 '16 at 13:05
  • Thanks all for your answers, I'll try that and let you know ! Sorry for the delayed response. – Anjou Apr 05 '16 at 13:26

2 Answers2

3

You can take a different approach on showing your camera screen. You can present this modally, which means it will slide up from the bottom over the tab bar. You can achieve this in the delegate method shouldSelectViewController which runs every time you are about to change index. If it is the camera then you present your VC but return NO so it doesn't change the tab index:

- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
    if(viewController == cameraNavigationController) {
        [self presentViewController:cameraNavigationController animated:YES completion:nil];
        return NO;
    }
    return YES;
}

You can then create the close button to dismiss the view controller back to the view before:

[self dismissViewControllerAnimated:YES completion:nil];
Joe Benton
  • 3,703
  • 1
  • 21
  • 17
  • Hi, sorry for the delay, I ran into some issus trying to implement your solution. I finally found a way around it, but I am not sure if it is the best one, could you check the Edit in my question ? Thanks for your answer (marked) – Anjou Apr 05 '16 at 17:23
1

I think your conceptual UI model is what is causing you the problem. Really, all tabs on a tab controller are normally always there. When you click a tab, you're switching the user into a tabbed experience. The user doesn't really want to leave (according to normal UI behaviours) that experience until they click a different tab.

But you're effectively saying to the user, "Click the camera tab to start the camera experience, and I will decide where you will go when you close the camera experience." Then in this post, you're kind of saying, "But it feels inelegant for me to redirect the user to the same tab, or to the tab from whence you arrived at the camera experience." I'd say that your senses are telling you something is not quite right (and I agree with them).

If you're going to implement the model of deciding that users can't stay in the camera experience until they switch out of it themselves, then you'll have to make your emotions happy with that model. And you'll have to decide where they go when they close the camera. There really are only a few choices - go back to some tab always (ugh); go back to their previous tab, treating the camera tab as a modal experience (inconsistent with the whole tab model, but better than a fixed return tab); or to illustrate a point, redirect the user to some random tab (double ugh).

I think if the camera tab is going to behave like a tab, it needs to be a two-step process. Switch to the camera tab, and then from the camera tab, modally present the full screen camera, returning to the camera tab when they close the full screen.

Otherwise, I'd say give up on the tab controller, and put buttons down below, so you can present the camera experience modally, full screen from a button.

Kevin
  • 1,548
  • 2
  • 19
  • 34
  • Thank you for the elaborate answer. I agree with your point but my idea was to recreate something like Instagram or Vine UX, where when the user click the camera tab, the section takes directly all the screen with a "go back" or "close" button. I'll think about your idea and see what fits best for my application but just in case, what do you think about the solution proposed by Benton and my way to implement it (you can see in the EDIT 2) Thanks again – Anjou Apr 05 '16 at 19:35
  • I think that your solution is a creative way to get around the problem. :-) But of course the problem is caused by your creative use of the tab bar UI... :-) And apparently, Instagram and Vine also do the same kind of thing. When is a tab button not a tab button? -- when it's a camera tab button--and then it acts like a tesseract tab button! :-) – Kevin Apr 05 '16 at 22:42