Programatically load a navigationController on tabitem click - ios

I have made a UITabBarController where when selecting one of the item I load an imagepicker.Now after user chose an image from picker i want to load a navigation controller, which I'm not able to. My code is
extension BaseTabBarController : UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if self.viewControllers?.index(of:viewController) == 2 {
// Presenting image picker here
present(self.imagePicker, animated: true, completion: nil)
return false
} else {
return true
}
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
}
}
here's the code after user has picked image
extension BaseTabBarController : ImagePickerDelegate {
func doneButtonDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) {
//This is the StoryboardID of Navigationcontroller i want to goto
let detailVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "UploadNavigationController")
//This is not working
present(detailVC, animated: true,completion: nil)
}
}
Here controller is not shown and I get this warning
Warning: Attempt to present <UINavigationController: 0x7fb4969c4800> on <BaseTabBarController: 0x7fb49780ea00> whose view is not in the window hierarchy!
I want to show the navigation controller with the tab bar still visible in the bottom

I guess, as you want to show the navigation controller with the tab bar still visible in the bottom, then you should first dismiss image picker then push new controller not present.
Because if you present any controller will cover whole screen as it is presenting from root controller. If you push then it will push from current controller and tabor remains as it was.
extension BaseTabBarController : ImagePickerDelegate {
func doneButtonDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) {
self.imagePicker.dismiss(animated: true) {
//This is the StoryboardID of Navigationcontroller i want to goto
let detailVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "UploadNavigationController")
//This is not working
let nav = UINavigationController(rootViewController: detailVC)
navigationController?.pushViewController(nav, animated: true)
//present(detailVC, animated: true,completion: nil)
}
}

Related

Loading a new viewController on the same tab bar item not working

So I am having a problem loading a new view controller on the same tab bar item. I have a tab bar controller as my root controller and 4 tabs on the tab bar. One of which is the account tab, when the user is logged in he would need to see an overview of his account details but when there is no user there needs to be a sign-in / login page on that tab bar item. For the moment I have a custom tab bar class for my tabbarcontroller with a function that I thought was the solution but nothing happens. I have put a breakpoint in the view controller that needs to loaded (the user detail page) and it comes in the ViewDidLoad so it loads but it doesn't appear on the screen.
Hope I can finally solve this problem!
Kind regards
B.
this is my custom tab bar controller class:
import UIKit
class TabBarViewController: UITabBarController, UITabBarControllerDelegate {
var window: UIWindow?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController) -> Bool{
let frame = UIScreen.main.bounds
window = UIWindow(frame: frame)
let index = tabBarController.viewControllers?.firstIndex(of: viewController)
if index == 3{ // Index of Account tab
let mainStoryBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = mainStoryBoard.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
window?.rootViewController = loginVC
window?.makeKeyAndVisible()
}
return true// you decide
}
}
here is the code that presents full screen loginVc
func tabBarController(_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController) -> Bool{
let frame = UIScreen.main.bounds
window = UIWindow(frame: frame)
let index = tabBarController.viewControllers?.firstIndex(of: viewController)
if index == 1{ // Index of Account tab
let mainStoryBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = mainStoryBoard.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
loginVC.modalPresentationStyle = .fullScreen
self.present(loginVC, animated: false, completion: nil)
}
return true// you decide
}
If you also want to have bottom bar ... your that viewController should be Navigation Controller and you can push your desired ViewController then like
func tabBarController(_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController) -> Bool{
let frame = UIScreen.main.bounds
window = UIWindow(frame: frame)
let index = tabBarController.viewControllers?.firstIndex(of: viewController)
if index == 1{ // Index of Account tab
let mainStoryBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = mainStoryBoard.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
// loginVC.modalPresentationStyle = .fullScreen
if let navigation = viewController as? UINavigationController {
navigation.pushViewController(loginVC, animated: false)
}
}
return true// you decide
}
It will show back button and navigationBar on top
if you dont want NavigationBar then in your MessagesViewController did load function call
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.isNavigationBarHidden = true
// Do any additional setup after loading the view.
}
And if you just want to hide back button then call
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.setHidesBackButton(true, animated: false);
// Do any additional setup after loading the view.
}
Changing your rootViewController is bad practice in Swift.
The correct way to handle tasks like this is to override your accountViewController's viewWillAppear function to determine if the user isn't logged in and present your loginViewController. Try something like:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// authManager would be a custom class that manages authorization with a property for loggedIn
if authManager.loggedIn == false {
guard let loginVC = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "login") as? LoginViewController else { return }
self.present(loginVC, animated: true, completion: nil)
}
}

How to PopUp or Modally Present View Controller from Tab Bar Controller?

How can I create a popup that appears when a button on a Tab Bar is pressed? I would like something similar to this: https://www.youtube.com/watch?v=zDWSaItF2ko.
I have tried many solutions but none have worked.
For example, I have tried this with my main view controller:
This still doesn't work though. How would I go about creating this. I know that I need to present the view controller modally and over current context, but how would I do that from a tab bar controller.
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController is PopupViewController {
if let popupView = tabBarController.storyboard?.instantiateInitialViewController() {
popupView.modalPresentationStyle = .fullScreen
tabBarController.present(popupView, animated: true, completion: nil)
return false
}
}
return true
}
Here are some pictures that may help:
Main Storyboard:
Popup Storyboard:
View Controller Code:
Have you tried debug this using breakpoints in Xcode? From what I can see, the first thing you do is to check whether the view controller that should be selected is of class PopupViewController. Are you sure the view controller is being instantiated correctly?
And by the way I would recommend another way of instantiating the view controller from storyboard rather then:
tabBarController.storyboard?.instantiateInitialViewController()
The first thing is to head over to the storyboard file itself and in the view controller you are trying to instantiate change the Storyboard ID to something for example the class of the storyboard (PopupViewController in your case).
Next you will want to try to instantiate the storyboard itself using the init(name: String, bundle storyboardBundleOrNil: Bundle?) initialiser:
let storyboard = UIStoryboard(name: "Popup", bundle: nil)
And now instantiate the view controller using the storyboard variable like so:
let popupViewController = storyboard.instantiateViewController(withIdentifier: "PopupViewController") as! PopupViewController
Finally you can give it some extra configuration and present it on the tab bar controller:
popupViewController.modalPresentationStyle = .fullScreen
tabBarController.present(popupViewController, animated: true)
Edit
Addionally to make it more Swifty, I recommend guard statement for early exit. Finally the method could look something like this:
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard viewController is PopupViewController else { return true }
let storyboard = UIStoryboard(name: "Popup", bundle: nil)
let popupViewController = storyboard.instantiateViewController(withIdentifier: "PopupViewController") as! PopupViewController
popupViewController.modalPresentationStyle = .fullScreen
tabBarController.present(popupViewController, animated: true, completion: nil)
return false
}

Present a View Controller modally from a tab bar controller

I want to build a view with a camera. Something just like Instagram where there is a button in the middle that the user can click and the camera view shows up.
I implemented a code for the TabViewController in the AppDelegate but nothing happens, no animation or Presentation of the new ViewController.
Here is my AppDelegate:
import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate, UITabBarControllerDelegate {
var window: UIWindow?
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: ViewController) -> Bool {
if viewController is ViewController {
let storyboard = UIStoryboard(name: "Main.storyboard", bundle: nil)
if let controller = storyboard.instantiateViewController(withIdentifier: "cameraVC") as? ViewController {
controller.modalPresentationStyle = .fullScreen
tabBarController.present(controller, animated: true, completion: nil)
}
return false
}
return true
}
Here is my Storyboard:
Any ideas?
I suggest to create a custom class for your TabBarController, then assign the delegate to that.
You can either assign and check the restorationIdentifier of the view controller, or do a type check. I usually use storyboard identifier as the restoration identifier of the view controller(s).
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if let identifier = viewController.restorationIdentifier, identifier == "cameraVC" {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "cameraVC") as! CameraViewController
present(vc, animated: true, completion: nil)
return false
}
return true
}
}
Here's a sample you can play with:
https://gist.github.com/emrekyv/3343aa40c24d7e54244dc09ba0cd95df
I just tried and It worked perfectly for me:
Create a Custom class for your TabBarController and assign it to your Controller in Storyboard.
After that override didSelect of tabBarController and write your presentation code there :
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if let controller = self.viewControllers?[self.selectedIndex] as? ViewController {
controller.modalPresentationStyle = .fullScreen
self.present(controller, animated: true, completion: nil
}
}
Hope it helps!!

Present View Controller embedded in Navigation Controller apart of Tab bar controller modally

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

Present a View modally from a tab bar controller

How can I present a View modally from a tab bar controller so that the view goes over the actual one?
I want to build a view with a camera. Something just like "WhatsApp" or "Instagram" where there is a button in the middle that the user can click and the camera view shows up.
Additionally, the user should move the tab he was before when the close button was clicked.
This is how my ViewController is connected to the TabBarController:
I've had to implement something similar in an app I'm currently building, it's relatively straightforward to do, you need to implement a delegate method of UITabBarController in order to achieve this.
The delegate method you need to implement is:
tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool
Returning false from this method will stop the tab controller from selecting your tab, you then just need to implement your own logic to present the UIViewController programatically.
Here's an example:
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
// If your view controller is emedded in a UINavigationController you will need to check if it's a UINavigationController and check that the root view controller is your desired controller (or subclass the navigation controller)
if viewController is YourViewControllerClass {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let controller = storyboard.instantiateViewController(withIdentifier: "storyboardID") as? YourViewControllerClass {
controller.modalPresentationStyle = .fullScreen
self.present(controller, animated: true, completion: nil)
}
return false
}
// Tells the tab bar to select other view controller as normal
return true
}
I've not tested the above code as my implementation is slightly different and has more variables. The general principle is the same.
Let me know how you get on and I'll update the answer if necessary.
Assuming that you are conforming to UITabBarControllerDelegate, you could implement:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
// here, you should edit "0" to be matched with your selected item
// for instance, if there is 5 items and the desired item is in the middle, the compared value should be "2"
if tabBarController.selectedIndex == 0 {
// simply, you will need to get the desired view controller and persent it:
let desiredStoryboard = UIStoryboard(name: "Main", bundle: nil)
let desiredViewController = desiredStoryboard.instantiateViewController(withIdentifier: "storyboard id")
present(desiredViewController, animated: true, completion: nil)
}
}
let modalVC = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerIdentifier")
modalVC.modalTransitionStyle = .crossDissolve
modalVC.modalPresentationStyle = .full or .overfullscreen // please check which of the options work
self.present(modalVC, animated: true, completion: {
})

Resources