Set Back title by the default for UINavigationController - ios

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.

Related

navigationItem.leftBarButton is not appearing (Xcode Swift 4 IOS)

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)

UISplitView's MasterViewController and Navigation Issue

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.

Set all back buttons like leftBarButtonItem with custom image

How do I set a custom image to all back buttons of view controllers pushed in a UINavigationController?
My issues are:
must look like leftBarButtonItem, position-wise (because the backBarButtonItem itself is too glued to the left and I can't seem to change it's horizontal alignment).
has to be on all back actions (instead of manually setting on each view controller).
having a method setCustomBackButton and calling it on each view controller is also not an option, I'm looking for something like UINavigationBar.appearance(), i.e., throughout the app.
Something like this:
But with the back action working without me manually setting the selector on each view controller.
UPDATE: In response to Joe's solution, I'm getting that error:
UINavigationBar.appearance().backIndicatorImage = UIImage(named: "backArrow")
See Here: https://www.raywenderlich.com/108766/uiappearance-tutorial
Below answer based on the following OP answers:
Custom Back Button With Image and How to remove all navigationbar back button title
Try below code in didFinishLaunchingWithOptions method in AppDelegate.
To setting up a custom back button:
let backArrowImage = UIImage(named: "back") // set your back button image here
let renderedImage = backArrowImage?.withRenderingMode(.alwaysOriginal)
UINavigationBar.appearance().backIndicatorImage = renderedImage
UINavigationBar.appearance().backIndicatorTransitionMaskImage = renderedImage
To hide a back button title:
let barAppearace = UIBarButtonItem.appearance()
barAppearace.setBackButtonTitlePositionAdjustment(UIOffsetMake(0, -60), for:UIBarMetrics.default)
Output: Updated
Update:
You need to add the following code to your More Information viewController to keep the title position.
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
You can create your own subclass of UINavigationController and change the button inside the navigationController(_:willShow:animated:) delegate method as follows:
class MyNavigationController: UINavigationController, UINavigationControllerDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
interactivePopGestureRecognizer?.delegate = self
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if viewController != self.viewControllers.first { // don't add button to rootViewController
let backButton = UIBarButtonItem(image: UIImage(named: "backArrow"), style: .plain, target: self, action: #selector(popViewController(animated:)) )
viewController.navigationItem.leftBarButtonItem = backButton
}
}
}
Theoretically the above delegate method could live anywhere, but this way its logical and easy to select where you want to have this functionality.
Also don't forget to set the interactivePopGestureRecognizer delegate for not loosing the edge swipe gesture to go back (this somehow breaks when setting a new leftBarButtonItem).
The above method could be further improved by keeping track of which view controllers were already shown and then only replace the leftBarButtonItem on new ones (right now it also replaces it when going back/popping to an already shown view controller).
Try this Swift 4.2
extension YouFirstViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if !(viewController is YouFirstViewController) {
let backButton = UIBarButtonItem(image: UIImage(named: "icnBack"), style: .plain, target: self, action: #selector(popview))
viewController.navigationItem.leftBarButtonItem = backButton
}
}
#objc func popview() {
navigationController?.popViewController(animated: true)
}
}
onYouFirstViewController
class YouFirstViewController: UIViewcontroller {
override func viewDidLoad() {
self.navigationController?.delegate = self
}
}

Swift: Setting Different Navigation Bar Buttons for Different Tabs

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!

Layout issues when presenting a view controller with UIModalPresentationStyleCurrentContext

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

Resources