I created a custom UITabBarController with 5 tabs in Swift 3.0. I've been trying to set different navigation bar items for certain tabs, but it's not working well.
Situation: I put codes that would change the Navigation Bar Buttons in each tab's viewDidLoad().
//The customized Tab Bar controller instance. Contained in each of the tabs.
var mainController: TabBarController?
override func viewDidLoad() {
// ... more code
setupNavBar()
// ... more code
}
func setupNavBar() {
// ... more code
mainController?.navigationItem.leftBarButtonItem = UIBarButtonItem(image: friendsImage, style: .plain, target: self, action: #selector(handleFindFriends))
// ... more code
}
Problem: Let's say Tab #1 is supposed to have NavBarButton A and Tab #2 is supposed to have NavBarButton B.
When I switch from Tab #1 to Tab #2, the code works fine; the NavBarButton changes from A to B.
However, when I click on Tab #1, the NavBarButton still remains B.
How can I make it so that the navigation bar buttons change accordingly even when I click on a tab I was on previously?
I assume you embed a UITabBarController (or it's subclass) into a UIViewController. This is wrong because in this case you tend to make the view controller generic which is usually a bad practice.
Instead I would suggest to change hierarchy of view controller to what you see on the image below.
UPDATE
If you do it in code:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let tabBarController = UITabBarController()
// first tab
let firstViewController = UIViewController()
_ = firstViewController.view
firstViewController.title = "First"
let firstNavigationController = UINavigationController(rootViewController: firstViewController)
// sets a specific button ("Friends") on a navigation bar for the first screen
firstViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Friends", style: .plain, target: nil, action: nil)
// second tab
let secondViewController = UIViewController()
_ = secondViewController.view
secondViewController.title = "Second"
let secondNavigationController = UINavigationController(rootViewController: secondViewController)
// sets a specific button ("Photos") on a navigation bar for the second screen
secondViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Photos", style: .plain, target: nil, action: nil)
// embeds the navigation controllers into the tab bar controller
tabBarController.viewControllers = [firstNavigationController, secondNavigationController]
// creates a window with the tab bar controller inside
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = tabBarController
window?.makeKeyAndVisible()
return true
}
}
I actually found a solution XD
In TabBarController:
override func viewWillAppear(_ animated: Bool) {
//Original code to set up tabs
//Code I added:
for i in 0...4 {
tabBar.items?[i].tag = i
}
}
//Code I added:
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
//Code to run when a certain tab is selected
}
Still, thanks a lot!
Related
I have made a UITableViewController(iOS controller for table views) and attempted to put a logout button in the top left corner of the screen with this code:
class ViewController: UITableViewController {
var logoutButton : UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
logoutButton = UIBarButtonItem(title: "Logout", style: .plain, target: self, action: #selector(handleLogout))
navigationItem.leftBarButtonItem = logoutButton
}
The iOS simulator just shows an empty (white) table view. I thought this would be a pretty simple task and have no idea why its not working or where to go from here. Any help would be appreciated.
Update: I have also tried to add navigation buttons to UINavigationController and UIViewController; the code looks like this
class ViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
let logoutButton = UIBarButtonItem(title: "Logout", style: .plain, target: nil, action: nil)
navigationItem.leftBarButtonItem = logoutButton
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let logoutButton = UIBarButtonItem(title: "Logout", style: .plain, target: nil, action: nil)
navigationItem.leftBarButtonItem = logoutButton
}
My AppDelegate looks like this:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
Nothing works. I cannot get a button to display. I don't want to use storyboards but I'm left with few other options if I can't even get a button to load. Any help is appreciated. Thanks!
The problem was in the appDelegate. The rootViewController should have been set like:
window?.rootViewController = UINavigationViewController(rootViewController: UITableViewController())
As opposed to:
window?.rootViewController = UITableViewController()
I think this is what the comments were trying to point out; thanks!
in this case you are have to push a loginViewController again at a logout time instead you need to popToRootViewControllerAnimated code
use this code on your logout button clickEvnet:
[self.navigationController popToRootViewControllerAnimated:YES];
if you are using swift then use this code
self.navigationController?.popToRootViewControllerAnimated(true)
I am updating my existing app to include a SplitView for iPads.
I have it working with a UITabBar, but am having an issue with my masterViewController as it is generating a "duplicate" navigation bar that is covering my existing navigation items on all masterViewControllers (tabs), including searchBar on the search tab.
The code I have is:
AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
let splitViewController = self.window!.rootViewController as! UISplitViewController
splitViewController.delegate = self
splitViewController.preferredPrimaryColumnWidthFraction = 0.33
splitViewController.minimumPrimaryColumnWidth = 375
splitViewController.preferredDisplayMode = .allVisible
return true
}
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
return true
}
The reason for having this in the AppDelegate is I saw an example where placing it hear will allow me not to require the code in each of the different Master Views (each tab). Have yet to test this as still working on the first master view.
Master View
override func viewDidLoad()
{
self.extendedLayoutIncludesOpaqueBars = true
self.navigationItem.hidesBackButton = true
// 3D Touch
if traitCollection.forceTouchCapability == .available {
registerForPreviewing(with: self as UIViewControllerPreviewingDelegate, sourceView: view)
ThreeDTouch = true
}
self.addSwitchVewButtonToNavigationBar()
self.addCategoryButtonToNavigationBar()
}
func addSwitchVewButtonToNavigationBar() {
let switchButton = UIButton(type: UIButtonType.custom)
let editImage = UIImage(named: "CollectionButton")?.withRenderingMode(.alwaysTemplate)
switchButton.setImage(editImage, for: .normal)
switchButton.addTarget(self, action: #selector(SpeciesViewController.onSwitchView), for: UIControlEvents.touchUpInside)
let switchButtonFinal = UIBarButtonItem(customView:switchButton)
self.navigationItem.rightBarButtonItem = switchButtonFinal
}
#IBAction func onSwitchView(_ sender: UIBarButtonItem)
{
AppDelegate.getAppState().isListViewSelected = false
let speciesColletion = storyboard?.instantiateViewController(withIdentifier: Resource.SpeciesCollectionStoryboard) as! SpeciesCollectionViewController
self.navigationController?.viewControllers = [speciesColletion]
}
Originally, the onSwitchViewButton was embedded using the IB, but did not work. This is the same system used for the addFavorite on the Detail View.
The problem that you addressing wrong UINavigation controller. I assume before splitting both master and SpeciesViewController existed in same navigation environment, and use same navigation controller. But in split view they don't. Your detail controller is actually UINavigation controller you are looking for, that had to control all navigation, and had to have buttons you need. You can get it from master as:
guard let split = splitViewController, let navController = split.viewControllers.last as? UINavigationController else { return }
And make sure that you split controller not embedded into another UINavigationController (reason for second navigation bar).
EDIT:
Function to return detail's Nav Controller:
var detailsNavigationController: UINavigationController? {
return splitViewController?.viewControllers.last as? UINavigationController
}
To access blue call detailsNavController, to access red use navigationController.
This is how
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
VAXApplicationSettings.configureApplicationAppearance()
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.makeKeyAndVisible()
if let window = window {
guard let welcomeViewController = storyboard.instantiateViewControllerWithIdentifier("WelcomeViewController") as? VAXWelcomeViewController else {
return nil
}
let navigationController = UINavigationController(rootViewController: welcomeViewController)
window.rootViewController = navigationController
}
return true
}
On the Welcome View Controller I push New View Controller which looks like this:
And as you see here is a Cancel button.
This is a source code of class of pushed view controller:
import UIKit
class NewViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .Plain, target: self, action: #selector(back))
}
func back()
{
navigationController?.popViewControllerAnimated(true)
}
}
This has nothing to do with your navigation controller or your navigation bar. The back button's title is, by default, the title of the view controller you would go back to if you tapped it. That view controller can also change the title of the back button that goes back to it. There is no overall "default"; it happens on a view controller by view controller basis.
i've set this for navigation item in my Welcome View controller which is root view controller of navigation controller.
For situations like this I like to create a sublass of UIViewController called BaseViewController Then all my view controllers inheirit from this instead of the standard iOS one. This allows me to add functionality that is applied throughout the app.
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.backButtonItem = UIBarButtonItem(title: "Back", style: .Default, target: self, action: #selector(back))
}
func back() {
self.navigationController?.popViewController(animated: true)
}
}
Ideally your app should be built from the start using this but you can do an app-wide find/replace to update your existing view controllers.
I want to create an app that lets the user click on a button on V1 on the navigation bar. This will segue to V2 where they can press another button on the navigation bar on V2 and it will bring them back to V1 which has the tab bar controller at the bottom. I don't want the tab bar on V2 and I don't want V2 as a tab bar item. When I try this the tab bar disappears on V1 when I segue back to V1 from V2.
TAB BAR CONTROLLER -> TAB BAR ITEM (V1) -> V2(Via navigation bar button item on V1) -> back to V1(Via navigation bar button item on V2)
I have added [self.navigationController, popViewControllerAnimated:YES]; to my button function but it comes up with an error - expected expression in container literal.
Apart from this code I have not got any other code in my app yet.
I am using Xcode 8.0 and Swift 3.0
First create subclass of UITabBarController, Then add properties to AppDelegate
var navController: UINavigationController?
var tabController: MyTabController?
If you want to show tab bar controller on app launch then put these code in AppDelegate in didFinishLaunchingWithOptions
self.window = UIWindow(frame: UIScreen.main.bounds)
let myStoryboard = UIStoryboard(name: "Main", bundle: nil) as UIStoryboard
self.tabController = myStoryboard.instantiateViewController(withIdentifier: "MyTabController") as? MyTabController
//self.navController = UINavigationController(rootViewController: self.tabController!)
//self.window?.rootViewController = self.navController
self.window?.rootViewController = self.tabController
self.window?.makeKeyAndVisible()
return true
If you want to jump on tab bar after login or something else then , add property to that controller
var appDelegate: AppDelegate!
in viewDidLoad
appDelegate = UIApplication.shared.delegate as? AppDelegate
And method should like
func logIntoApp() {
appDelegate.tabController = self.storyboard?.instantiateViewController(withIdentifier: "MyTabController") as? MyTabController
appDelegate.window?.rootViewController = appDelegate.tabController
}
Then in your tab item view controller , create property of AppDelegate and assign delegate as above.
And methods should be like :
#IBAction func showWithTab(_sender: AnyObject) {
let DefaultVC = self.storyboard?.instantiateViewController(withIdentifier: "DefaultViewController") as! DefaultViewController
self.navigationController?.pushViewController(DefaultVC, animated: true)
}
#IBAction func showWithoutTab(_sender: AnyObject) {
let DefaultVC = self.storyboard?.instantiateViewController(withIdentifier: "DefaultViewController") as! DefaultViewController
// You can create your own animation
UIView.transition(from: (appDelegate.tabController?.view)!, to: (appDelegate.navController?.view)!, duration: 0.3, options: UIViewAnimationOptions.curveEaseIn) { (finished) in
self.appDelegate.window?.rootViewController = self.appDelegate.navController
}
// OR you can use like this way
UIView.transition(from: self.view, to: DefaultVC.view, duration: 0.3, options: UIViewAnimationOptions.curveEaseIn) { (finished) in
self.appDelegate.window?.rootViewController = self.appDelegate.navController
}
}
Why not use a simple Tab Bar?
Something like this:
and in VC1 :
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
myTab.removeFromSuperview()
}
I have solved my problem after days of trying to figure out an answer. You need to add this to any view controller.
#IBAction func unwindToViewController (sender: UIStoryboardSegue){
}
Then you can add a segue from bar button item to the exit icon on the view controller.
You can view an image of the VC scene here.
I ran into a problem having to do with UISplitViewController and modal view controllers on the iPad, so I tried to reproduce the issue in a small project to see if I could figure out what was going on. Unfortunately, the problem seems to still be occurring, but I can't figure out why.
I've included a very small, complete Swift program below that reproduces the problem. Basically, if you run that code in a new Swift iOS project on the iPad, you'll see the following behavior:
The app will start with the following UI:
If I tap Present, a new modal controller will present over the detail side of the split controller. If I tap Dismiss on that modal controller, I'll return to the same initial interface and all is well.
However, if I tap Present, then tap Present on the modal controller that appears (so I have two modal controllers over the original details view), and then dismiss the top one, the RightViewController takes over the entire screen, eliminating the split view controller:
I've been banging my head against this all afternoon. Is there something I'm missing?
Here is the complete source for the example application:
import UIKit
class RightController: UIViewController {
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.title = "Right"
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
super.loadView()
let label = UILabel(frame: CGRect(x: 0, y: 100, width: 100, height: 20))
label.text = "Top Left"
self.view.addSubview(label)
let presButton = UIBarButtonItem(title: "Present", style: .Plain, target: self, action: Selector("present:"))
self.navigationItem.rightBarButtonItem = presButton
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Dismiss", style: .Plain, target: self, action: Selector("dismiss:"))
}
func dismiss(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
func present(sender: AnyObject) {
let rc = RightController()
let nav = UINavigationController(rootViewController: rc)
nav.modalPresentationStyle = .CurrentContext
self.presentViewController(nav, animated: true, completion: nil)
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let splitViewController = UISplitViewController()
splitViewController.delegate = self
let nav1 = UINavigationController(rootViewController: UIViewController())
nav1.title = "Left"
let nav2 = UINavigationController(rootViewController: RightController())
splitViewController.viewControllers = [nav1, nav2]
self.window!.rootViewController = splitViewController
return true
}
}
extension AppDelegate: UISplitViewControllerDelegate {
func splitViewController(svc: UISplitViewController, shouldHideViewController vc: UIViewController, inOrientation orientation: UIInterfaceOrientation) -> Bool {
return false
}
}
EDIT: Cycling through the device's orientations causes the split view controller to redraw itself correctly, at least until Dismiss is pressed again.
EDIT: I can also get this to work if I use the new .OverCurrentContext presentation style in iOS 8. However, I can't drop compatibility with iOS 7, so I need a different solution.
I have a solution that works although I will be the first to admit, it does feel a little hacky. I think a big part of your issue stems from a change in .CurrentContext and after some testing I found that it functions differently for iOS 7 and 8+. So everything works expectedly if you choose the proper style based on the iOS version:
var presStyle: UIModalPresentationStyle = (UIDevice.currentDevice().systemVersion as NSString).integerValue == 7 ? .CurrentContext : .OverCurrentContext
nav.modalPresentationStyle = presStyle