Present Modally UIViewController From UITabBarController Programmatically - ios

Hi everyone I need to present a View Controller modally when the user selects the index 1 of my Tab bar.
I created a UITabBarController class where I instantiate all the view controllers to be shown with the tabBar
In this part of the code I manage the modal presentation of the view controller for the index 1 of the tabBar
The problem is that when I select index 1 the VCIndex1 controller is called twice ... once for the normal display of the tabBar and another time for the modal presentation
How can I present VCIndex1 modally without the tab bar calling the controller x2 times?
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
tabBar.barTintColor = UIService.Color.backgroundColor
tabBar.isTranslucent = false
tabBar.tintColor = UIService.Color.primaryColor
tabBar.selectedItem?.badgeColor = UIService.Color.secondaryColor
tabBar.unselectedItemTintColor = UIService.Color.tertiaryColor
tabBar.shadowImage = UIImage()
let vcIndex0 = UINavigationController(rootViewController: VC0())
vcIndex0 = UIImage(systemName: "rosette")
let vcIndex1 = UINavigationController(rootViewController: VC1())
vcIndex1 = UIImage(systemName: "plus.square.on.square")
let vcIndex2 = UINavigationController(rootViewController: VC2())
vcIndex2 = UIImage(systemName: "tag")
viewControllers = [vcIndex0, vcIndex1, vcIndex2]
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
let indexOfTab = tabBar.items?.firstIndex(of: item)
if indexOfTab == 1 {
let vc = VC1()
vc = .fullScreen
present(vc, animated: true, completion: nil)
}
}
}

You probably want to implement shouldSelect (Apple Docs) and handle your tab-detection and modal presentation there.
Give this a try:
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
tabBar.barTintColor = .lightGray // UIService.Color.backgroundColor
tabBar.isTranslucent = false
tabBar.tintColor = .green // UIService.Color.primaryColor
tabBar.selectedItem?.badgeColor = .blue // UIService.Color.secondaryColor
tabBar.unselectedItemTintColor = .cyan // UIService.Color.tertiaryColor
tabBar.shadowImage = UIImage()
let vcIndex0 = UINavigationController(rootViewController: VC0())
vcIndex0.tabBarItem = UITabBarItem(title: "0", image: UIImage(systemName: "rosette"), tag: 0)
// just create a plain UIViewController here (it will never be seen)
//let vcIndex1 = UINavigationController(rootViewController: VC1())
let vcIndex1 = UIViewController()
vcIndex1.tabBarItem = UITabBarItem(title: "1", image: UIImage(systemName: "plus.square.on.square"), tag: 0)
let vcIndex2 = UINavigationController(rootViewController: VC2())
vcIndex2.tabBarItem = UITabBarItem(title: "2", image: UIImage(systemName: "tag"), tag: 0)
viewControllers = [vcIndex0, vcIndex1, vcIndex2]
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController == tabBarController.viewControllers?[1] {
let vc1 = VC1()
vc1.modalPresentationStyle = .fullScreen
present(vc1, animated: true, completion: nil)
return false
}
return true
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
// if you want to do something based on selected tab
if let indexOfTab = tabBar.items?.firstIndex(of: item) {
print("didSelect:", indexOfTab)
}
}
}
class VC0: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
}
}
class VC1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissMe))
view.addGestureRecognizer(tap)
}
#objc func dismissMe() -> Void {
dismiss(animated: true, completion: nil)
}
}
class VC2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
}
}

Related

Config Navigation bar only work if viewControllers are created inside viewWillAppear in UITabBarController

I have a UITabBarController with 2 ViewControllers: HomeViewController and ProfileViewController
Inside HomeViewController, I have to change the NavigationBar appearance with the following code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let nav = self.navigationController {
nav.navigationBar.layer.zPosition = 0
}
let logo = UIImage(named: "plentinaText")?.withRenderingMode(.alwaysTemplate)
let container = UIView(frame: CGRect(x: 0, y: 0, width: 1000, height: 0))
let imageView = UIImageView(image: logo)
imageView.tintColor = .white
imageView.contentMode = .scaleAspectFit
container.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: container.topAnchor),
imageView.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -10),
imageView.centerXAnchor.constraint(equalTo: container.centerXAnchor),
imageView.widthAnchor.constraint(equalToConstant: view.frame.width)
])
self.tabBarController?.navigationItem.titleView = container
}
Problem is, this block of code only work if I create HomeViewController ProfileViewController inside viewWillAppear in UITabBarController.
If I put it in ViewDidLoad, it don't even have a NavigationBar. But when I push to another vc and go back to HomeViewController, the Nav bar will work as I wanted.
//
// HomeTabbarController.swift
// Plentina
//
// Created on 10/29/21.
//
import UIKit
class HomeTabbarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
UITabBar.appearance().barTintColor = PlenitaColors.buttonBackGround // your color
if #available(iOS 13.0, *) {
UITabBar.appearance().unselectedItemTintColor = UIColor.systemGray4
} else {
UITabBar.appearance().unselectedItemTintColor = UIColor.lightGray
}
UITabBar.appearance().tintColor = .white
if #available(iOS 15.0, *) {
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = PlenitaColors.buttonBackGround
tabBar.standardAppearance = appearance
tabBar.scrollEdgeAppearance = appearance
}
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// tabbar 1
let storyboard = UIStoryboard(name: "Custom", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "photo") as? HomeViewController
let item1 = viewController
let unselectedIcon1 = UIImage(named: "tabbarHome")?.withRenderingMode(.alwaysTemplate)
let selectedIcon1 = UIImage(named: "tabbarHome")!.withRenderingMode(.alwaysTemplate)
let icon1 = UITabBarItem(title: "Loans", image: unselectedIcon1, selectedImage: selectedIcon1)
item1!.tabBarItem = icon1
// tabbar 2
let unselectedIcon2 = UIImage(named: "tabbarProfile")?.withRenderingMode(.alwaysTemplate)
let selectedIcon2 = UIImage(named: "tabbarProfile")!.withRenderingMode(.alwaysTemplate)
let item2 = ProfileViewController()
let icon2 = UITabBarItem(title: "Profile", image: unselectedIcon2, selectedImage: selectedIcon2)
item2.tabBarItem = icon2
let controllers = [item1!, item2] // array of the root view controllers displayed by the tab bar interface
self.viewControllers = controllers
}
// Delegate methods
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
print("Should select viewController: \(viewController.title ?? "") ?")
return true
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
I found a workaround. I just need to leave self.viewControllers = controllers in viewWillAppear and move the rest to ViewDidLoad, but I still don't know why?
class HomeTabbarController: UITabBarController, UITabBarControllerDelegate {
var controllers: [Any]?
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
UITabBar.appearance().barTintColor = PlenitaColors.buttonBackGround // your color
if #available(iOS 13.0, *) {
UITabBar.appearance().unselectedItemTintColor = UIColor.systemGray4
} else {
UITabBar.appearance().unselectedItemTintColor = UIColor.lightGray
}
UITabBar.appearance().tintColor = .white
if #available(iOS 15.0, *) {
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = PlenitaColors.buttonBackGround
tabBar.standardAppearance = appearance
tabBar.scrollEdgeAppearance = appearance
}
// tabbar 1
let storyboard = UIStoryboard(name: "Custom", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "photo") as? HomeViewController
let item1 = viewController
let unselectedIcon1 = UIImage(named: "tabbarHome")?.withRenderingMode(.alwaysTemplate)
let selectedIcon1 = UIImage(named: "tabbarHome")!.withRenderingMode(.alwaysTemplate)
let icon1 = UITabBarItem(title: "Loans", image: unselectedIcon1, selectedImage: selectedIcon1)
item1!.tabBarItem = icon1
// tabbar 2
let unselectedIcon2 = UIImage(named: "tabbarProfile")?.withRenderingMode(.alwaysTemplate)
let selectedIcon2 = UIImage(named: "tabbarProfile")!.withRenderingMode(.alwaysTemplate)
let item2 = ProfileViewController()
let icon2 = UITabBarItem(title: "Profile", image: unselectedIcon2, selectedImage: selectedIcon2)
item2.tabBarItem = icon2
controllers = [item1!, item2] // array of the root view controllers displayed by the tab bar interface
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.viewControllers = controllers as? [UIViewController]
}
// Delegate methods
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
print("Should select viewController: \(viewController.title ?? "") ?")
return true
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}

UINavigationController in UITabBarController not completely wraps UIViewController

I have an UITabBarController as my rootViewcontroller which has 3 UINavigationController for each Tab. Each UINavigationController has an initial UIViewController which just has a red background color.
My Problem is that the UINavigationController does not completely cover the UIViewController at the first start. After switching the tabs it covers the UIViewController. So what am I doing wrong here?
Thanks in advance.
AppDelegate:
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = TabBarViewController()
window?.makeKeyAndVisible()
UITabBarController:
class TabBarViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Navigation Tab
let navVC = NavigationViewController()
// Departure Tab
let depVC = DeparturesViewController()
// Settings Tab
let setVC = SettingsViewController()
self.viewControllers = [
createNavigationController(title: "Navigation", rootViewController: navVC, imageName: "map"),
createNavigationController(title: "Abfahrten", rootViewController: depVC, imageName: "station"),
createNavigationController(title: "Einstellungen", rootViewController: setVC, imageName: "user"),
]
}
private func createNavigationController(title: String, rootViewController: UIViewController, imageName: String) -> UINavigationController {
rootViewController.title = title
let nc = UINavigationController(rootViewController: rootViewController)
nc.title = title
nc.view.backgroundColor = .white
nc.navigationBar.prefersLargeTitles = true
nc.navigationController?.navigationItem.largeTitleDisplayMode = .always
nc.tabBarItem.image = UIImage(named: imageName)?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate)
return nc
}
override func viewWillAppear(_ animated: Bool) {
self.selectedIndex = 0
}
}
The very simple UIViewController:
class NavigationViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .red
}
}
After removing following code form the TabBarController it completely wraps the Viewcontroller
override func viewWillAppear(_ animated: Bool) {
self.selectedIndex = 0
}
But I can't explain why

Swift - UITabBarController as ChildViewController is not working

I need to add UITabBarController as a subview of RootViewController, but that UITabBarController can't be touched.
Here is my code. How can i fix it?
func addSubviewToSelf(){
var tabVC = TabBarVC()
addChildViewController(tabVC)
self.view.addSubview(tabVC.view)
tabVC.didMove(toParentViewController: self)
tabVC.view.snp.makeConstraints{ (make) in
make.top.bottom.left.right.equalTo(self.view)
}
}
Here is the simplified version of adding UITabBarController as childView of UIViewController, I'm using NSLayoutAnchor API in place of SnapKit
import UIKit
class RootViewController: UIViewController {
override func loadView() {
view = UIView()
view.backgroundColor = .white
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
addChildVC()
}
func addChildVC() {
let tabBarVC = TabBarVC()
addChild(tabBarVC)
view.addSubview(tabBarVC.view)
tabBarVC.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tabBarVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tabBarVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tabBarVC.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tabBarVC.view.topAnchor.constraint(equalTo: view.topAnchor)
])
tabBarVC.didMove(toParent: self)
}
}
The UITabBarController subclass is below
class TabBarVC: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
// Do any additional setup after loading the view.
configureTabBarItems()
}
func configureTabBarItems() {
let vc1 = UIViewController()
vc1.view.backgroundColor = .orange
vc1.tabBarItem = UITabBarItem(tabBarSystemItem: .search, tag: 0)
let vc2 = UIViewController()
vc2.view.backgroundColor = .yellow
vc2.tabBarItem = UITabBarItem(tabBarSystemItem: .bookmarks, tag: 1)
let navigationController1 = UINavigationController(rootViewController: vc1)
let navigationController2 = UINavigationController(rootViewController: vc2)
setViewControllers([navigationController1, navigationController2], animated: false)
tabBar.tintColor = .red
tabBar.unselectedItemTintColor = .black
}
}
As you have not given UITabBarController subclass implementation, you may create some UIViewControllers and assign it to setViewControllers(_ viewControllers::[UIViewControllers]?, animated: Bool) method. Also set tintColor and unselectedItemTintColor properties and check if works for you. Finally Clean Build project and run, it should work for you.
If the Tabbarvc is about for all App , I think you can call in AppDelegate. Write this code in didFinishLaunch in AppDelegate
window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "TabBarStoryBoard", bundle: Bundle.main)
let viewController = storyboard.instantiateInitialViewController()
window?.rootViewController = viewController
window?.makeKeyAndVisible()
return true

How to set up a title at the top of a ViewController

In my app I have a tabBarViewController, and this is the first bar:
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
}
override func viewWillAppear(_ animated: Bool) {
self.navigationItem.title = "RECENTS"
}
}
this is the custom class:
class CustomTabBar: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let firstVC = FirstViewController()
let secondVC = SecondViewController()
let thirdVC = ThirdViewController()
firstVC.tabBarItem.title = "RECENT"
secondVC.tabBarItem.title = "MAP"
thirdVC.tabBarItem.title = "SETTINGS"
firstVC.title = "RECENT"
secondVC.title = "MAP"
thirdVC.title = "SETTINGS"
viewControllers = [firstVC,secondVC,thirdVC]
}
}
So my question is: How can I put a title at the top of the view controller?
In the previous code I tried, but it's not working!
Something like this:
The title property is shown when a view controller is wrapped in a UINavigationController. Here's some code replacing the last line (i.e. the viewControllers assignment) in of your example that should do the trick:
let firstNC = UINavigationController(rootViewController: firstVC)
let secondNC = UINavigationController(rootViewController: secondVC)
let thirdNC = UINavigationController(rootViewController: thirdVC)
viewControllers = [firstNC,secondNC,thirdNC]
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.navigationItem.title = "My Title"
}
This should work. I hope it helps you.
Or if you can try this.
let vc3 = UINavigationController(rootViewController: SearchVC())
vc3.tabBarItem.image = UIImage(named: "vyhledavani")
vc3.title = "Vyhledat"
vc3.tabBarItem.tag = 2
viewControllers = [vc3, ...]

UITabBar Controller Present ViewController of Second tab Without Segue

I'm eliminating the storyboard from my app completely. How do I present the VC that is linked to the second tab of the TabBarController.
Setup: mainVC --- myTabBar -- tab1 - navCntrl - VC1
tab2 - navCntrl - VC2
When using a segues I used the following code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == myTabBar) {
let tabVC = segue.destination as? UITabBarController {
tabVC.selectedIndex = myTabBarIndex ==> 1 to reach VC2
}
// other other stuff
}
To eliminating the segues I rewrote the above but although I set the selectedIndex VC2 is not presented. Any suggestions?
func vc2Btn() {
let tabVC = MyTabBar()
tabVC.selectedIndex = 1 // ==>> Index set but can not reach VC2
present(tabVC, animated: true, completion: nil)
}
The full code of my test system:
class MyTabBar: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create Tab 1
let navCtrlTab1 = UINavigationController(rootViewController: VC1())
let tabOne = navCtrlTab1
let tabOneBarItem = UITabBarItem(title: "", image: StyleKit.imageOfIconTabRecent, selectedImage: StyleKit.imageOfIconTabRecentRev)
tabOne.tabBarItem = tabOneBarItem
// Create Tab 2
let navCtrlTab2 = UINavigationController(rootViewController: VC2())
let tabTwo = navCtrlTab2
let tabTwoBarItem = UITabBarItem(title: "", image: StyleKit.imageOfIconTabNote, selectedImage: StyleKit.imageOfIconTabNoteRev)
tabTwo.tabBarItem = tabTwoBarItem
self.viewControllers = [tabOne, tabTwo]
}
}
class mainVC: UIViewController {
let btn0: UIButton = {
let button = UIButton()
button.setBackgroundImage(StyleKit.imageOfBtnBlue(btnText: "VC1"), for: UIControlState.normal)
button.addTarget(self, action:#selector(vc1Btn), for: .touchUpInside)
return button
}()
let btn1: UIButton = {
let button = UIButton()
button.setBackgroundImage(StyleKit.imageOfBtnBlue(btnText: "VC2"), for: UIControlState.normal)
button.addTarget(self, action:#selector(vc2Btn), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(btn0)
self.view.addSubview(btn1)
addConstraintsWithFormat("H:|-100-[v0]", views: btn0)
addConstraintsWithFormat("H:|-100-[v0]", views: btn1)
addConstraintsWithFormat("V:|-300-[v0]-20-[v1]", views: btn0, btn1)
}
func addConstraintsWithFormat(_ format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
func vc1Btn() {
let tabVC = MyTabBar()
tabVC.selectedIndex = 0 // ==>> this is working
present(tabVC, animated: true, completion: nil)
}
func vc2Btn() {
let tabVC = MyTabBar()
tabVC.selectedIndex = 1 // ==>> Index set but can not reach VC2
present(tabVC, animated: true, completion: nil)
}
}
class VC1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "VC1"
print ("VC1")
}
}
class VC2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "VC2"
print ("VC2")
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = mainVC()
return true
}
}

Resources