Programmatically embed view instance into navigation controller - ios

I created a tab bar controller in which I used in the code:
let TV0000 = self.storyboard!.instantiateViewControllerWithIdentifier("V0003") as! V0003
self.setViewControllers([TV0000], animated: true)
let T0000 = UITabBarItem(title: D0000, image: nil, tag: 0)
TV0000.tabBarItem = T0000
But now i would like to have TV000 embedet into a navigation controller how I tryed to do it is by : (N is my custum nav controller class)
let N = self.storyboard!.instantiateViewControllerWithIdentifier("N") as! N
N.pushViewController(viewController: TV0000, animated: true)
self.setViewControllers([N], animated: true)
let T0000 = UITabBarItem(title: D0000, image: nil, tag: 0)
N.tabBarItem = T0000
All of this works fine the problem that I'm having is that the push of the navigation controller only seems to be non permanent and does not act like being embedded because when I go to another tab and come back the navigation bar is gone or it crash.
How could I make a Programmatic version of embeding the view controller instance into a new navigation controller instance ?

Related

iOS 13 In Tab Bar child view controller viewWillAppear is not called

I have a tab bar controller and I have added five view controllers in it like this:
class InfluencerMainTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let findWorkVC = UINavigationController.init(rootViewController: InfluencerFindWorkVC.instantiate(fromAppStoryboard: .Influencer))
findWorkVC.tabBarItem = UITabBarItem(title: nil, image: #imageLiteral(resourceName: "ic_home"), tag: 0)
let inboxVC = UINavigationController.init(rootViewController: InfluencerInboxVC.instantiate(fromAppStoryboard: .Inbox))
inboxVC.tabBarItem = UITabBarItem(title: nil, image: #imageLiteral(resourceName: "ic_inbox"), tag: 1)
let workDiaryVC = UINavigationController.init(rootViewController: InfluencerWorkDiaryVC.instantiate(fromAppStoryboard: .Influencer))
workDiaryVC.tabBarItem = UITabBarItem(title: nil, image: #imageLiteral(resourceName: "ic_work"), tag: 2)
let notificationsVC = InfluencerNotificationsVC.instantiate(fromAppStoryboard: .Influencer)
notificationsVC.tabBarItem = UITabBarItem(title: nil, image: #imageLiteral(resourceName: "ic_notification"), tag: 3)
let accountVC = InfluencerProfileVC.instantiate(fromAppStoryboard: .Influencer)
accountVC.tabBarItem = UITabBarItem(title: nil, image: #imageLiteral(resourceName: "ic_profile"), tag: 4)
let tabBarList = [findWorkVC, inboxVC, workDiaryVC, notificationsVC, accountVC]
viewControllers = tabBarList
self.tabBar.tintColor = UIColor.appPurpleColor
self.tabBar.barTintColor = UIColor.white
}
}
Problem is my first controller, which is findWorkVC, its viewWillAppear is getting called but when I click on any other view controller, their viewWillAppear are not getting called.
It is working fine pre iOS 13 devices but on iOS 13 its not just getting called and also the height of navigation bar is lesser than iOS 12's navigation bar height, you can see the title in navigation bar is just overlapping the status bar text.
I created a new project and tested out everything, view controllers with tabs, everything was working there but not in my project so I started looking for the things which were different in my project than a newly created project.
Turns out, it was the root view controller. I was setting root view controller like this with animation
let controller = InfluencerMainTabBarController.instantiate(fromAppStoryboard: .Main)
UIView.transition(from: self.view, to: controller.view, duration: 0.6, options: [.transitionFlipFromTop], completion: { completed in
UIApplication.shared.keyWindow?.rootViewController = controller
})
So I simply presented the view controller with modalPresentationStyle = .fullScreen without animation and everything worked.
let controller = InfluencerMainTabBarController.instantiate(fromAppStoryboard: .Main)
controller.modalPresentationStyle = .fullScreen
DispatchQueue.main.async { UIApplication.shared.keyWindow?.rootViewController = controller }
Now I only have to look for how to set root view controller with animation. :|
If your presentation style is not the new default by Apple (sheet), than just set the presentation style for all your ViewControllers (NavigationController included) to FullScreen. This way the viewWillAppear method will be called again for every VC.

What is different between this when we navigate one view controller to another

What is the difference between this first call:
let next = self.storyboard?.instantiateViewController(withIdentifier: "AFVC") as! AddFileViewController
self.present(next, animated: true, completion: nil)
and this second:
let dashboard = self.storyboard?.instantiateViewController(withIdentifier: "DBVC") as! DashboardViewController
self.navigationController?.pushViewController(dashboard, animated: true)
The first usage will present the new view controller. This presentation normally slides the new controller up from the bottom. If you want to go back, you need to create a button or something similar to dismiss it.
The second usage will use the navigation controller to display (via push which normally slides in from the right) the new view controller. You will automatically get a "< Back" button in the navigation bar. But this will only work if the calling view controller is already embedded in a navigation controller, otherwise self.navigationController is nil.

Tabbar not showing when i open from side menu

I am using this code
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "PrintMainViewController")
self.navigationController!.pushViewController(controller, animated: true)
and also added:
self.tabBarController?.tabBar.isHidden = false
If I understand your hierarchy correctly you have a tab bar controller which has navigation controllers in it. So basically any of the tabs can push additional view controllers and tab bar is still visible.
Now you want to push some new controller on the currently selected view controller in the tab bar and you want to do it from another part of the app, another view controller that has no relation to tab bar.
The quickest way to do that is to expose a static instance of your tab bar view controller. This will only work if you always have only 1 tab bar controller in your application (probably 99% of the applications).
First add a current instance to your tab bar view controller:
class MyTabBarViewController: UITabBarController {
static private(set) var currentInstance: MyTabBarViewController?
override func viewDidLoad() {
super.viewDidLoad()
MyTabBarViewController.currentInstance = self
}
}
So when view loads a static value is assigned and can now be accessed anywhere in your project via MyTabBarViewController.currentInstance.
The rest is then just accessing the currently selected view controller and pushing a new view controller. Something like this should do:
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "PrintMainViewController")
(MyTabBarViewController.currentInstance?.selectedViewController as? UINavigationController)?.pushViewController(controller, animated: true)
You must push in TabBarController
self.tabBarController?.pushViewController(controller, animated: true)

UITabBarItem's title does not shown when using with Navigation controller

I'm having a trouble with my very simple app.
My app simply have 2 mains UIViewControllers called News and Category, each of them has their own UINavigationController.
So in AppDelegate.swift, I've done like this
window = UIWindow(frame: UIScreen.main.bounds)
let tabBarController = UITabBarController()
let newsVC = NewsVC()
// CREATE TAB BAR ITEM WITH TITLE ONLY
let newsVCTabBarItem = UITabBarItem(title: "News", image: nil, tag: 1)
newsVC.tabBarItem = newsVCTabBarItem
let categoryVC = CategoryVC()
// CREATE TAB BAR ITEM WITH TITLE ONLY
let categoryVCTabBarItem = UITabBarItem(title: "Category", image: nil, tag: 2)
categoryVC.tabBarItem = categoryVCTabBarItem
let rootViewControllers = [newsVC, categoryVC]
// CREATE NAVIGATION CONTROLLER FOR EACH OF THEM
tabBarController.viewControllers = rootViewControllers.map {
UINavigationController(rootViewController: $0)
}
window?.rootViewController = tabBarController
window?.makeKeyAndVisible()
When I run this simple application, the tab bar items do not show anything :(
But when I change the UITabBarItem to system's styles like this
let newsVCTabBarItem = UITabBarItem(tabBarSystemItem: .featured, tag: 1)
It's working perfectly! So hard to understand!
So does anyone know why my title-only tab bar item does not working? Have I missed something important?
Thanks in advance!
Add title property to the ViewControllers to show the title in UITabBarItem.
var title: String? { get set }
Set the title to a human-readable string that describes the view. If
the view controller has a valid navigation item or tab-bar item,
assigning a value to this property updates the title text of those
objects.
let newsVC = ViewController()
newsVC.title = "News"
.....
let categoryVC = ViewController2()
categoryVC.title = "Category"
.....
Or
Assign an image to the UITabBarItem to see the result.
let newsVCTabBarItem = UITabBarItem(title: "News", image: UIImage(named: "news.png"), tag: 1)
....
let categoryVCTabBarItem = UITabBarItem(title: "Category", image: UIImage(named: "category.png"), tag: 2)
.....
Update:
Tab bar items are configured through their corresponding view
controller. To associate a tab bar item with a view controller, create
a new instance of the UITabBarItem class, configure it appropriately
for the view controller, and assign it to the view controller’s
tabBarItem property. If you don't provide a custom tab bar item for
your view controller, the view controller creates a default item
containing no image and the text from the view controller’s title
property.

Show a View Controller that is already on Navigation Stack

I have a Tab bar Controller (with a bottom menu) and also a top menu. The problem is that I don't want to link the yellow and green views to the tab bar (because the user is going to change the views using the top menu rather than the bottom menu).
I'm having a problem that every time I click the buttons a new instance of the view is going to stack (so I end up having something like V1 -> V2 -> V3 -> V2 -> V4 and so on)
My partial solution is to make something like this:
#IBAction func yellowViewButtonAction(_ sender: AnyObject)
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "YelloViewController") as! YelloViewController
if let viewControllers = navigationController?.viewControllers {
for viewController in viewControllers {
// some process
if viewController is YelloViewController {
print("View is on stack")
}
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "YelloViewController") as! YelloViewController
self.navigationController?.pushViewController(controller, animated: false)
}
}
I can see that the view is on navigation stack because the if statement inside the for is true. The question is, how can I retrieve it instead of pushing a new instance of the same view? (Because besides the huge memory problem that this present I also lose any data I had on the view).
I want to keep everything on the stack intact.
Example:
V1 -> V2 -> V3 -> V4 (current view)
If I go back to V1 from V4 I still want to have V4, V3 and V2 on the navigation controller stack.
Another question is that if this solution is something that Apple might refuse.
I appreciate any help.
looks like you don't use and need navigation controller. Whenever you call self.navigationController?.pushViewController(controller, animated: false) a new instance of that controller is on its way to stack.
Ideally you would call popViewController from that view controller where you have navigated. When creating custom behavior of tab bar controller it is quite difficult to get the navigation logic exactly as you planned, at least in my opinion.
In cases like this I usually take care of showing and hiding view controllers manually.
#IBAction func didPressTab(sender: UIButton) {
let previousIndex = selectedIndex
selectedIndex = sender.tag
buttons[previousIndex].selected = false
let previousVC = viewControllers[previousIndex]
previousVC.willMoveToParentViewController(nil)
previousVC.view.removeFromSuperview()
previousVC.removeFromParentViewController()
sender.selected = true
let vc = viewControllers[selectedIndex]
addChildViewController(vc)
vc.view.frame = contentView.bounds
contentView.addSubview(vc.view)
vc.didMoveToParentViewController(self)
}
where every 'navigation button' has unique id and calls didPressTab function.
I actually learnt this from this tutorial: https://github.com/codepath/ios_guides/wiki/Creating-a-Custom-Tab-Bar
Pops view controllers until the specified view controller is at the top of the navigation stack.
Reference - https://developer.apple.com/documentation/uikit/uinavigationcontroller
func popToViewController(UIViewController, animated: Bool) -> [UIViewController]?

Resources