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.
Related
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
I am using MVVM Arch with RxSwift, I have one screen commonly used for more than 8 flows, Here I don't want to use enum and switch casses but I have to call different functions based on flow and route to different screens
Ex: I have Two classes A and B and both will go to C, Here C have one button with two functions Now once we navigate to C and I click button in C then it should call function related to A class and route to some other controller
How can I do this without enums
Mean I have to decide in A or B classes which function should call in C after clicking on button
class ViewControllerA: UIViewController {
override func viewDidLoad() {
}
#IBAction func btn_clicked(_ sender: Any) {
let vc: ViewControllerC =
self.storyboard?.instantiateViewController(withIdentifier:
"ViewControllerC") as! ViewControllerC
vc.isFrom = "A"
self.navigationController?.pushViewController(vc, animated: true)
}
}
class ViewControllerB: UIViewController {
override func viewDidLoad() {
}
#IBAction func btn_clicked(_ sender: Any) {
let vc: ViewControllerC =
self.storyboard?.instantiateViewController(withIdentifier:
"ViewControllerC") as! ViewControllerC
vc.isFrom = "B"
self.navigationController?.pushViewController(vc, animated: true)
}
}
class ViewControllerC: UIViewController {
var isFrom = ""
override func viewDidLoad() {
super.viewDidLoad()
if isFrom == "A"{
self.callForA()
}else if isFrom == "B"{
self.callForB()
}
}
func callForA(){
//Your Code
}
func callForB(){
//Your Code
}
}
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"
}
}
}
}
}
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.
I have the following code that i'm trying to achieve a "scroll to top" on a UIViewcontroller and "scroll to beginning" on another UIViewController.
The first VC has a table view and the second VC has a collection view.
-NearMeViewController-
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.delegate = self
//....
}
extension NearMeViewController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 0 {
self.nearMeTable.setContentOffset(CGPoint.zero, animated: true)
}
}
}
-MapSearchViewController-
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.delegate = self
//....
}
extension MapSearchViewController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBarIndex = tabBarController.selectedIndex
if tabBarIndex == 2 && requests.mapEventData.count > 0 {
self.resultsCollectionView?.scrollToItem(at: IndexPath(row: 0, section: 0),
at: .left,
animated: true)
}
}
}
They are both connected to the same UITabBar of course.
The issue is that when i open the app, and i press the tabBarIndex 0 it scrolls to top of the table view.
Then i change VC and i try to press the tabBarIndex 2, so it takes me to the first item of the collection view.
Afterwards I go back to the first VC and when i try to press the tabBarIndex 0, the tableview is not scrolling to top (as it was doing when i first opened the app).
But the second VC with the collection view is working fine.
Any idea why this might happen?
viewDidLoad is not being called each time the VC appears but only once (or when it was deallocated before)
the delegate of vc 0 is set only once... and then vc1 is loaded and set as delegate... but then not vc0 again.
to make it work, set the delegate in viewWillAppear.
note
changing the delegate over and over for this is weird... and checking the index is fragile at best go with DonMag's solution.
It looks like you are checking the tabBarController's .selectedIndex ... but that is the index of the currently showing tab, not the tab you are "on your way to."
More likely, you want to use code similar to this:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if let vc = viewController as? MapSearchViewController {
// user tapped Map Search tab item
}
if let vc = viewController as? NearMeViewController {
// user tapped Near Me tab item
}
}
Edit: Here is a simple example, using a subClassed UITabBarController.
Set up your storyboard as normal, creating View Controllers and connecting them as "Tabs", then set the Custom Class of the TabBarController to MyTabBarController. This sample code will (hopefully) be easy to follow.
A runnable example is at: https://github.com/DonMag/SWTabBarSubclass
//
// TabBarSample.swift
//
import UIKit
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// all your normal setup stuff....
}
func firstViewSpecific(_ aValue: Int) -> Void {
print("Passed value is:", aValue)
// do something with the passed value
}
}
class NearMeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// all your normal setup stuff....
}
func resetMe() -> Void {
print("resetMe() called in NearMeVC")
// do whatever you want, such as
self.nearMeTable.setContentOffset(CGPoint.zero, animated: true)
}
}
class MapSearchViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// all your normal setup stuff....
}
func resetMe() -> Void {
print("resetMe() called in MapSearchVC")
// do whatever you want, such as
if requests.mapEventData.count > 0 {
self.resultsCollectionView?.scrollToItem(at: IndexPath(row: 0, section: 0),
at: .left,
animated: true)
}
}
}
class MyTabBarController: UITabBarController, UITabBarControllerDelegate {
var myValue = 0
override func viewDidLoad() {
super.viewDidLoad()
// make self the UITabBarControllerDelegate
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if let vc = viewController as? FirstViewController {
myValue += 1
vc.firstViewSpecific(myValue)
return
}
if let vc = viewController as? NearMeViewController {
vc.resetMe()
return
}
if let vc = viewController as? MapSearchViewController {
vc.resetMe()
return
}
}
}
might not apply to all scenarios, check if your VC has been set to
self.tabBarController?.delegate = self
within your ViewDidLoad()