I need to change tabbar selected item when view updates. So, i have tabbar with 4 items, i want that when clicking on the button that is in the last index, view updates and selected item also stays the last(3). But when i am clicking on the button, the selected item changes and it shows firstviewcontroller. I am using this code in button click
let tabBarVC = UIStoryboard(name: "TabBarViewController", bundle: nil).instantiateViewController(withIdentifier: "CustomTabBarViewController")
let nav = UINavigationController(rootViewController: tabBarVC)
nav.setAsRoot()
When i set selected item to 3 in CustomTabBarViewController's viewDidload it is okay, but i need that when clicking on button that works not always when tabbar is showing.
If you need to refresh all the vcs
let tabBarVC = UIStoryboard(name: "TabBarViewController", bundle: nil).instantiateViewController(withIdentifier: "CustomTabBarViewController") as! CustomTabBarViewController
tabBarVC.selectedIndex = 3
let nav = UINavigationController(rootViewController: tabBarVC)
nav.setAsRoot()
But if you need to refresh only the last vc then
let tabBarVC = self.tabBarController as! CustomTabBarViewController
let vc = // load only 3rd vc from storyboard with it's identifier
tabBarVC.viewControllers![3] = vc
The basic idea is that the tabBarController:shouldSelectViewController: selector in your UITabBarController delegate is called whenever the user clicks on tab item.
Thus, by appropriately defining that method, you get a chance to do your own processing before the current view controller is replaced by the one the user selected by clicking in the tab bar.
So, simply return NO from this selector in case you wish to prevent the current view controller to be replaced, i.e. when a transaction is ongoing.
import UIKit
class TabBarViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self // Delegate self to handle delegate methods.
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
let selectedIndex = tabBarController.viewControllers?.firstIndex(of: viewController)!
if selectedIndex == 3 { // <- The index for which you don't want to set view.
// Do anything.
return false
}
return true
}
}
Related
I have an app that has 4 tab bar items (A, B, C, D). I'm using notification data to direct a user to a child vc of A ('A-child') and I want to populate the search bar in A-child with a value from the notification.
I'm successfully getting the notification and value, that's fine. I'm also able to navigate to A just fine using:
let tabBarController = UIApplication.shared.keyWindow?.rootViewController as! UITabBarController
tabBarController.selectedIndex = 0
But I then need to navigate to A-Child VC from A and then set the search bar text (I'm less worried about the search bar text piece as I think I can work that out once I get the right VC stack in place). I could of course use a segue to go straight from where the user when they tap on the notification to A-child, but then I lose the expected navigation behaviour for the user from A-child.
I know I'm not the first to ask a question like this, and I've gone through everything I can find on SO relating to this - but can't make any of the answers click. Help is much appreciated!
Edit:
I've got it partially working with this:
if let tabbarController = UIApplication.shared.keyWindow?.rootViewController as?
UITabBarController {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "AChildViewController") as!
AChildViewController
vc.searchString = "text"
tabarController.present(vc, animated: true, completion: nil)
}
Not sure if this is an appropriate way or I'm going to get myself in trouble
It isn't being presented inside the navigation controller - so I'm not getting the top nav bar (including the critical search bar)
It isn't being presented inside the tab bar controller
when I tried to do tabBarController.navigationController? < the navigation controller is nil
EDIT 2 - Solution:
Found an unaccepted answer from a couple of years ago that did the trick for me via: https://stackoverflow.com/a/51763243/12481584
let tabBar: UITabBarController // get your tab bar
tabBar.selectedIndex = 0 // eg. zero. To be sure that you are on correct tab
if let navigation = tabBar.viewControllers?[tabBar.selectedIndex] as? UINavigationController {
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
if let chatViewController = storyboard.instantiateViewController(withIdentifier: "chatViewController") as? ChatViewController {
navigation.pushViewController(chatViewController, animated: true)
}
}
The question is not so clear, but I assume your problem is basically how to navigate to a child controller of TAB A from anywhere.
There are multiple ways to do this (deep-linking), but the most straight forward way literally just do your usual approach of pushing, popping, presenting, dismissing of controllers and combine local storage of your data that indicates where you should redirect the user to after tapping a push notification or deep-linking from anywhere such as a website.
An extension of getting the current or top most screen should help, for instance, the is how I do it:
import UIKit
var windowRootController: UIViewController? {
if #available(iOS 13.0, *) {
let windowScene = UIApplication.shared
.connectedScenes
.filter { $0.activationState == .foregroundActive }
.first
if let window = windowScene as? UIWindowScene {
return window.windows.last?.rootViewController
}
return UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController
} else {
return UIApplication.shared.keyWindow?.rootViewController
}
}
/// Category for any controller.
extension UIViewController {
/// Class function to get the current or top most screen.
class func current(controller: UIViewController? = windowRootController) -> UIViewController? {
guard let controller = controller else { return nil }
if let navigationController = controller as? UINavigationController {
return current(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return current(controller: selected)
}
}
if let presented = controller.presentedViewController {
return current(controller: presented)
}
return controller
}
}
Now, onto your specific problem. So assuming you really now handle the redirection to TAB A properly, the next thing you would do is push the Child A after going to the TAB A, and then in the Child A didAppear, put the text in the searchBar and do the searching.
I have a view controller like below.
This view is attached with a tabBarController. The tabBarController has 5 viewControllers and I have to present the 5th viewController of tabBar from another page. So I used the below codes for present that viewController
#IBAction func onClickUserProfile(_ sender: Any) {
let navVc = self.storyboard?.instantiateViewController(withIdentifier: "ProfileVC")as! ProfileVC
navVc.userId = Int(self.userId)
navVc.navigationItem.hidesBackButton = true
navVc.tabBarController?.tabBar.isHidden = false
self.navigationController?.pushViewController(nxtVc, animated: true)
}
But after execute the code it resulting the view controller as the below image.
The view undergoes the tabBar. Anyone help me to push to a tabBar view.
You need to set the selected UIViewController from UITabBarController something like this should work .
self.tabBarController?.selectedViewController = self.tabBarController?.viewControllers![1]
where tabBarController?.viewControllers returns the array of current ViewControllers embedded in the UITabBarController .
Your code should be something like this.
#IBAction func onClickUserProfile(_ sender: Any) {
let vc = self.tabBarController?.viewControllers![1] as! ProfileVC // use your index
vc.userId = Int(self.userId)
self.tabBarController?.selectedViewController = vc
}
Note: Don't create an instance of the UIViewController as .instantiateViewController(withIdentifier:) use the already existed
ones in the array tabBarController?.viewControllers, creating new
instance will be treated as new one and gives the problem you have up
there .
I have implemented a tabViewController with some pages. one of those page is a tableview controller. after clicking on tableview item i am opening another viewController. Now i want to go back to that specific page. the selectedIndex of that tab Page is 1.
How can i do this?
Thanks for your help
store board
Try this
let tabBar: UITabBarController = self.window?.rootViewController as! UITabBarController
tabBar.selectedIndex = 1
If you are outside from tabBar Controller.
self.tabBarController?.selectedIndex = 1
If you are already in tabBar Controller and move to another tabBar Controller. Go to the ViewDid of TabBar method and paste the code.
override func viewDidLoad() {
super.viewDidLoad()
self.selectedIndex = 1
}
If you want the user to be able to go back to tableViewController after clicking on a cell you should use a navigationController instead.
You are using UITabBarController as rootViewController then can do this:
if let appDelegate = UIApplication.shared.delegate as? AppDelegate{
if let rootTabBarController = appDelegate.window?.rootViewController as? UITabBarController{
rootTabBarController.selectedIndex = 1
}
}
Because I want to redesign the tab bar UI, I wrote a custom tab bar controller according to https://github.com/codepath/ios_guides/wiki/Creating-a-Custom-Tab-Bar
In TabBarViewController's viewDidLoad(), define several subviews corresponding to each tab bar
homeViewController = storyboard.instantiateViewControllerWithIdentifier("HomeViewController")
...
viewControllers = [homeViewController, searchViewController, accountViewController, trendingViewController]
and the main method when tapping tab
#IBAction func didPressTab(_ sender: UIButton) {
let previousIndex = selectedIndex
selectedIndex = sender.tag
tabButtons[previousIndex!].isSelected = false
let previousVC = viewControllers[previousIndex!]
// remove previous VC
previousVC.willMove(toParentViewController: nil)
previousVC.view.removeFromSuperview()
previousVC.removeFromParentViewController()
// set current VC
sender.isSelected = true
let vc = viewControllers[selectedIndex]
addChildViewController(vc)
// Adjust the size to match content view
vc.view.frame = contentView.bounds
contentView.addSubview(vc.view)
vc.didMove(toParentViewController: self)
}
I could set a default tab bar index selectedIndex when the tab bar view is loaded. However, how can I switch to next tab bar in homeViewController (without tapping tab bar buttons)?
This doesn't work in homeViewController
TabBarViewController().tabButtons[2].isSelected = true TabBarViewController().didPressTab(TabBarViewController().tabButtons[2])
I'm not sure how to get the running tab controller, set the selectedIndex, and update subview in the subview controllers.
All you need to do is to call tabBar.setSelectedViewController: and pass the view controller.
If you only know the tab index, you call tabBar.viewControllers[index] and get the view controller.
I finally use Delegate to solve.
In SubViewController add protocol
protocol SubViewControllerDelegate {
func transferToView(index: Int)
}
and declare this in the class
var delegate: SubViewControllerDelegate?
In TabBarViewController set to conform SubViewControllerDelegate
Implement the method
func transferToView(index: Int) {
tabButtons[index].isSelected = true
didPressTab(tabButtons[index])
}
Set delegate
subViewController = storyboard.instantiateViewControllerWithIdentifier("HomeViewController")
let subVC = subViewController as! SubViewController
subVC.delegate = self
I have a SplitViewController with a UITableViewController as the masterViewController and a UIViewController as the detailViewController.
When the user taps a cell, I need to push to a new UITableViewController. So I added a segue from the cell to a UITableViewController. But what happens is the UITableViewController gets added to the masterViewController's stack.
How can I push to a whole new UITableViewController from the masterViewController?
Here is a simple example how I approach such functionality (I created a new Master-Detail Application):
Storyboard:
Notice that the root VC is now a UINavigationController. Therefore AppDelegate must be changed accordingly:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let navCtr = self.window!.rootViewController as UINavigationController
let splitViewController = navCtr.visibleViewController as UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController
navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem()
splitViewController.delegate = self
return true
}
And then finally in MasterViewController add this:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if indexPath.row % 2 == 0 {
self.performSegueWithIdentifier("showDetail", sender: nil)
} else {
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
if let let rootCtr = appDelegate.window?.rootViewController {
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let newSplit = storyboard.instantiateViewControllerWithIdentifier("SplitVC") as UISplitViewController
/// Because of Apple's "Split View Controllers cannot be pushed to a Navigation Controller"
let yetAnotherNavCtr = UINavigationController(rootViewController: newSplit)
rootCtr.presentViewController(newSplit, animated: true, completion: nil)
}
}
}
Important notes:
If you create new MasterDetail Application from the template, you have to disconnect the showDetail segue, because it is directly linked to the cell's selected callback. If you want to preserve that functionality as well, simply connect it again not from the cell itself, but from the whole VC. To be able to use it as in my funky didSelect... method that performs the showDetail segue on even rows.
The Split View presentation will work only once - I haven't implemented the whole rootViewController replacement - the lldb will complain saying: Attempt to present UISplitViewController on UINavigationController whose view is not in the window hierarchy! if you'll try to do it for the second time. But that's really up to your requirements for how you want the app to behave.
Name the SplitView Controller in Storyboard "SplitVC" (Storyboard ID) if you want to present the Split View Controller like I am doing in my code.
I got it! First I should mention Michal's answer helped me to get an idea and point me in the right direction so thanks to him.
What I did was simple actually. Before I had a View Controller with a container view embedding the split view controller. I simply went ahead and embedded that view controller in a navigation controller.
Then I added the view controller I want to segue to in the storyboard but no segue attached to it. I'm doing it programatically in the masterViewController's didSelectRowAtIndexPath method.
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
if let rootVC = appDelegate.window?.rootViewController {
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let mapVC = storyboard.instantiateViewControllerWithIdentifier("MapVC") as! UIViewController
rootVC.showViewController(mapVC, sender: nil)
}
I get a reference to the rootViewController which is a navigation controller through the AppDelegate and I push the new view controller I want to it's stack.
That's it! Here's a demo project in case anyone's interested.