Swift 4 - How to switch between ViewControllers from a UITabBarController programmatically - ios

I'm using a UITabBarController and I would like to use a button in a view to move to the next view without having to click on the bar.
How could I do that programmatically ?
class PlayTableBarViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
}
func nextView() {
print(self.viewControllers!)
self.selectedViewController = self.viewControllers![self.selectedIndex + 1]
self.show(self.selectedViewController!, sender: nil)
}
}
class StartVC: UIViewController {
private var _fatherVC: PlayTableBarViewController?
var fatherVC: PlayTableBarViewController {
if _fatherVC == nil {
_fatherVC = self.storyboard?.instantiateViewController(withIdentifier: "playViewController") as? PlayTableBarViewController
}
return _fatherVC!
}
#IBAction func backToGame(_ sender: UIButton) {
fatherVC.nextView()
}
}

Use selectedIndex property of UITabBarController (until you want to have More section in your UITabBar.
From:
https://developer.apple.com/documentation/uikit/uitabbarcontroller/1621171-selectedindex
Setting this property changes the selected view controller to the one
at the designated index in the viewControllers array. To select the
More navigation controller itself, you must change the value of the
selectedViewController property instead.
class PlayTableBarViewController: UITabBarController {
func nextView() {
self.selectedIndex = self.selectedIndex + 1
}
}

I found the error where I initialize _fatherVC. This:
_self.storyboard?.instantiateViewController(withIdentifier: "playViewController")
as? PlayTableBarViewController_
should be:
_fatherVC = self.tabBarController as? PlayTableBarViewController
Now it's working perfectly.

Related

How to show different ViewControllers when tapping on tabBar in swift

I have three viewControllers and two of them connected to a tabBarController. So app should show one of the two vc’s depending Bool value when tapping on tabBar item here is my storyboard
import UIKit
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let userLoggedIn: Bool!
if tabBarController.selectedIndex == 1{
if userLoggedIn == true{
// show firstVC
}else{
// show secondVC
}
}
}
}
You can use childViewController with the parent is ViewController fromStoryboard.
override func viewDidLoad() {
super.viewDidLoad()
///add two ChildVC here, hide 1 ifneeded
}
When click to this tab you check userLoggedIn.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if userLoggedIn == true {
/// show first childVC
/// hide second childVC
} else {
/// hide first childVC
/// show second childVC
}
}
You can check demo: Demo
Simply add .overCurrentContext to modalPresentationStyle in your viewDidLoad like this.
let newVC = self.storyboard?.instantiateViewController(withIdentifier:"secondVC")
newVC?.modalPresentationStyle = .overCurrentContext
self.present(newVC!, animated: false, completion: nil)
Simply set the viewController list when the userLoggedIn variable is modified (assuming userLoggedIn is TabBarCOntroller instance variable) :
class TabBarC: UITabBarController {
#IBOutlet var firstVC : FirstViewController!
#IBOutlet var secondVC : SecondViewController!
#IBOutlet var thirdVC : ThirdViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
for vc in self.viewControllers! {
if let fv = vc as? FirstViewController {
firstVC = fv
} else if let fv = vc as? SecondViewController {
secondVC = fv
} else if let fv = vc as? ThirdViewController {
thirdVC = fv
}
}
}
var userLoggedIn : Bool = false {
didSet {
if userLoggedIn {
self.viewControllers = [firstVC, thirdVC]
} else {
self.viewControllers = [secondVC, thirdVC]
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self. userLoggedIn = false
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if item.tag == 3 { // For purpose of test i set tag for 3 VC to 1 for firsttVC, 2 for secondVC, 3 for thirdVC
// for test : change login value each time thirdVC is selected
userLoggedIn = ! userLoggedIn
}
}
So each time from anywhere in your code you setup userLoggedIn, the tabor show the wanted tab bar items.
The 3 VC are added to the tabbar in the stpryboard.
When selecting thirdVC, the first tab bar item changes between one and two

Reload a current viewcontroller in tab bar

Currently I have a view controller that when you tap on the tab at index 3 (Map Tab) it loads a view controller that contains a map (Map A). NOTE: it should always load Map A every time the Map Tab is pressed. There is a chiclet at index 0 on the main tab that when tapped allows you to switch from Map A to Map B and then takes you to that map (This switch is done manually so to keep the tab bar on screen using the tab bar's selectedIndex which means the viewWillAppear() doesnt seem to be called). NOTE: Map A and Map B share the same viewController, a bool is used to differentiate which one to load in viewWillAppear..The issue I'm having is after the chiclet is pressed to switch from Map A to Map B, once I hit the Map Tab again on the tab bar it automatically loads the current map I was just on (Map B), but as stated earlier, when pressed from tab bar, it should only load Map A.
This is what I was trying but it still won't show the proper map after the tab has been pressed:
class MainTabBarController: UITabBarController {
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
switch viewController {
case is MapViewController:
let nc = NotificationCenter.default
nc.post(name: Notification.Name("changeMapStatus"), object: nil)
}
}
class MapViewController: BaseViewController {
var mapBSelected: Bool = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
mapBSelected ? setupMapB() : setupMapA()
}
#objc func changeMapStatus() {
guard let mainTabController = tabBarController as? MainTabBarController else { return }
mainTabController.refreshMapTab()
self.MapBSelected = false
}
}
func refreshMapTab() {
let index = PrimaryFeatureTab.map.displayOrder// enum to determine tab order
DispatchQueue.main.async {
self.viewControllers?.remove(at: index)
self.viewControllers?.insert(PrimaryFeatureTab.map.rootViewController, at: index)
}
}
}
You can do the following
Create a closure in your FirstViewController like this:
enum MapType {
case mapA
case mapB
}
class MainViewController: UIViewController {
var mapSelectionClosure: ((MapType) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func aTapped(_ sender: Any) {
mapSelectionClosure?(.mapA)
}
#IBAction func bTapped(_ sender: Any) {
mapSelectionClosure?(.mapB)
}
}
Then you can set this closure in your MainTabBarController like this
class MainTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
guard let mainViewController = viewControllers?.first(where: { $0 is MainViewController }) as? MainViewController else {
return
}
mainViewController.mapSelectionClosure = { mapType in
self.setMapType(mapType)
}
}
func setMapType(_ type: MapType) {
guard let mapViewController = viewControllers?.first(where: { $0 is MapViewController }) as? MapViewController else {
return
}
mapViewController.selectedMapType = type
}
}
In MapViewController...
class MapViewController: BaseViewController {
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.setupMapA()
}
}
Now, when you leave the Map Tab, it will reset itself to Map A.
No need for any code in a custom TabBarController.

How to change tabBar item title by using button click from child viewController

I have UITabBarController using storyboard and I custom it by another UITabBarController class in order to perform some changes by requirement of my app.
For my UITabBarController has 4 child viewControllers. And my first child view I implemented one button in order to change title of TabBarItem.
This is my custom UITabBarController class
class TabbarViewController: UITabBarController {
override func awakeFromNib() {
super.awakeFromNib()
}
override func viewDidLoad() {
super.viewDidLoad()
guard let item = tabBar.items else {
return
}
for i in item {
print("This is tabbar item title \(i.title)")
}
}
func language(_ bool: Bool) {
if bool {
print("This is khmer")
self.tabsController?.tabBar.tabItems[0].title = "tab1"
self.tabsController?.tabBar.tabItems[1].title = "tab2"
self.tabsController?.tabBar.tabItems[2].title = "tab3"
self.tabsController?.tabBar.tabItems[3].title = "tab4"
} else {
self.tabsController?.tabBar.tabItems[0].title = "tab5"
self.tabsController?.tabBar.tabItems[1].title = "tab6"
self.tabsController?.tabBar.tabItems[2].title = "tab7"
self.tabsController?.tabBar.tabItems[3].title = "tab8"
}
}
}
I created language func in order to perform change the title of my tabBaritem and this func will be called by the first child viewController that I have mentioned above. But I don't know why it returns me nil? But when I list all tabBaritem in viewDidLoad and it not return nil.
This is first child viewController:
#IBAction func changeLanguageAction(_ sender: UIButton) {
if CustomLocale.shared.LANGUAGE_IDENTIFIER == "EN" {
if let tab = self.tabBarController as? TabbarViewController {
tab.language(true)
}
sender.setImage(R.image.khmerFlag(), for: .normal)
}else {
if let tab = self.tabBarController as? TabbarViewController {
tab.language(false)
}
sender.setImage(R.image.englishFlag(), for: .normal)
}
}
You can try
if let tab = self.tabBarController as? TabbarViewController {
tab.language(false) // or true
}
Note this TabbarViewController(). creates another instance
Solved. I just removed this code below in viewDidLoad and pass it to language func. That means we can call tabBar.items once. That's why it returns me nil when i try to call second time in language func.
guard let item = tabBar.items else {
return
}

Swift - Change var with selectedIndex

I'm trying to go from a ViewController1 to a ViewController2 by changing the selectedIndex of the tabBarController.
In ViewController1:
func action() {
_ = self.tabBarController?.selectedIndex = 4
}
In ViewController2:
class ViewController2: UIViewController {
var isOnInfo = true
}
My problem is pretty simple. I would like to change the value of isOnInfo in the action() function.
Is it possible?
Another way to do that would be to create a global var, but if I can avoid that it would be better.
Thank you for you answers.
I can recommend you to move your changing tabs logic in Tab bar controller.
Make your own class TabBarController(subclass of UITabBarController), set it as custom class of your tab bar controller at the storyBoard and move the function action() there.
import UIKit
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
}
func action(isOnInfo: Bool) {
selectedIndex = 4
if let viewControllers = self.viewControllers,
let viewController2 = viewControllers[selectedIndex] as? ViewController2 {
viewController2.isOnInfo = isOnInfo
}
}
}
Then in viewController1:
if let tabBarController = self.tabBarController as? TabBarController {
tabBarController.action(isOnInfo: false)
}
You can get object of viewcontroller at selected index in tabbarcontroller. once you have it, you can modify any public property.
func action() {
_ = self.tabBarController?.selectedIndex = 4
if let vc2 = self.tabBarController?.viewControllers![4] as?
ViewController2
{
vc2.isOnInfo = true
}
}

change a view programmatically with swift-slide-menu

I am using this library: swift-slide-menu
Is there any way to change a view programmatically? I am using the same viewcontroller to show two different datasets in a tableview. I have two buttons in the menu, one for each dataset. When clicking on button1 I go to the ListVC and everything works fine. When clicking on button2 I go to an empty ViewController that sets the url for the dataset and segues right away to ListVC.
When I do the segue, the menu button is not shown in ListVC. This is probably because I am not adding a childView to the BaseViewController when I segue. But is there any way to add a code inside my empty ViewController that adds ListVC as a childView, instead of doing a normal segue?
PS.
I am trying to prevent having the same code inside two different viewcontrollers and having 2 copies of the same vc in storyboard.
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
addChildView("AllDatasetID", titleOfChildren: "All data", iconName: "icon1") //ListVC
addChildView("FilteredDatasetID", titleOfChildren: "My data", iconName: "icon2") //EmptyVC that segues to ListVC
}
}
class EmptyVC: UIViewController {
override func viewDidAppear(_ animated: Bool) {
self.performSegue(withIdentifier: "toListVC", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toListVC" {
if let destinationVC = segue.destination as? ListVC {
destinationVC.theUrl = Settings.myFilterUrl
}
}
}
}
The solution was to access inside the ListVC a variable from the menu that stored the title for the current view. The menu subclasses BaseViewController and several variables are accessable from the BaseViewController through the navigationController.
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
addChildView("ListVCScreenID", titleOfChildren: "My datapoints", iconName: "icon1")
addChildView("ListVCScreenID", titleOfChildren: "All datapoints", iconName: "icon2")
}
}
class ListVC: UIViewController {
var theUrl: String?
override func viewDidLoad() {
super.viewDidLoad()
let vc = self.navigationController?.viewControllers
var counter = 0
for v in vc! {
if v as? ViewController != nil {
print("title is: \(v.title)")
if(v.title == "My datapoints") {
theUrl = "getMyData"
}
else {
theUrl = "getAllData"
}
}
}
}
}

Resources