I'm wondering what's the best way to achieve the following. My application when launches goes to the following tableview:
When you select a category, a segue to another tableview is made and looks like this:
What I want to do, is eventually have the 'Basket' barButtonItem in the first view to update with the total price of the basket amount. I also want the button to be visible from the entire navigation controller cycle.
Is there a way that I can have the Basket button show on every stage of the navigationcontroller process?
So for example I'd like to have the button show on the second table view.
Yes you can achieve it by use of take class of UINavigationController like below
class CustomNavigationController: UINavigationController, UINavigationControllerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
// MARK: Private Functions
private func addRightBarButtonTo(viewController: UIViewController){
barButtonItem = UIBarButtonItem(title: "Basket", style: .plain, target: self, action: #selector(CustomNavigationController.dismiss(_:)))
viewController.navigationItem.rightBarButtonItem = barButtonItem
}
// MARK: UINavigationController Delegate
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
self.addRightBarButtonTo(viewController)
}
#objc func dismiss(sender: Any){
self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
}
Use CustomNavigationController as rootView Controller of the Window.
Second Way
Take extension of UIViewController
extension UIViewController {
func addRightButtonItem() {
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Basket", style: .done, target: self, action: #selector(barButtonMethod(_:)))
}
#objc func barButtonMethod(_ sender: UIBarButtonItem) {
// Your code
}
}
and call below method in viewWillAppear of each and every viewController
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.addRightButtonItem()
}
Related
I have programmed UIBarButtonItem so that it does some action before switching to a previous view. I was wondering how do I get the viewcontroller of that transitioning scene from my UIBarButtonItem?
So, scene 1 -> scene 2 (current scene) -> scene 1 (after clicking the UIBarButtonItem button)
I've tried to pass the previous scene variables (that I need) to the current scene to perform action on (sense I don't think the transitioning scene is instantiating a new view, but that doesn't work
override func viewDidLoad() {
super.viewDidLoad()
loadTuple()
let addButton: UIBarButtonItem = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(saveExercise(_: )))
self.navigationItem.setRightBarButton(addButton, animated: true)
}
#objc func saveExercise(_ sender: UIBarButtonItem) {
self.addNewTupleToDB(element: self.getNewTuple())
self.saveData()
debugPrint("saveExercise")
self.exerciseVCTableView?.reloadData() // tried to pass the table view from the previous scene to call here
self.navigationController?.popViewController(animated: true)
// Want to save reload the table data of the scene this button transitions to
}```
You may use delegate pattern for solving this. Delegate pattern is something, to delegate some work to other and return to the work after delegation is done.
Suppose ViewController1 has UIBarButton , goes to ViewController2, some function done and return to ViewController1
let us take a protocol
protocol MyProtocol {
func myFunction()
}
then in ViewController2 add a delegate method. Assuming in ViewController2, you have to call a method doMyWork and some work will be done here, then you have to pop.
class ViewController2 {
var delegate : MyProtocol?
override func viewDidLoad() {
super.viewDidLoad()
doMyWork()
}
func doMyWork() {
// my works
delegate?.myFunction()
self.navigationController.popViewController()
}
}
now the viewController1 have to receive the delegate work has done.
in viewController1, in barButtonItem
class ViewController1 {
#objc func barButton(_sender : UIBarButton) {
let viewController = ViewController2()
viewController.delegate = self
self.naviagtionController.pushViewController(viewController, animated : true)
}
}
now you have to implement protocol method
extension ViewController1 : MyProtocol {
func myFunction() {
self.tableView.reloadData()
}
}
I want to change the appearance of all Back buttons in the app by setting the text to "Back" and removing the arrow (even just removing the arrow would be fine). I'm trying to find a way to do it globally for all view controllers in the app while also keeping the functionality (I don't want to create a new instance of a UIBarButtonItem and having to set the selector).
I created a custom UINavigationController and tried to set the back button title there with navigationItem.backBarButtonItem?.title = "Back", but it didn't work. Any suggestions how to do it properly?
Just create CustomNavigationController then at
override open func pushViewController(_ viewController: UIViewController, animated: Bool) do controlClearBackTitle method
then use CustomNavigationController to fix it all in your app
import UIKit
class CustomNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override open func pushViewController(_ viewController: UIViewController, animated: Bool) {
controlClearBackTitle()
super.pushViewController(viewController, animated: animated)
}
override open func show(_ vc: UIViewController, sender: Any?) {
controlClearBackTitle()
super.show(vc, sender: sender)
}
func controlClearBackTitle() {
self.navigationBar.backIndicatorImage = UIImage()
self.navigationBar.backIndicatorTransitionMaskImage = UIImage()
topViewController?.navigationItem.backBarButtonItem = UIBarButtonItem(title: "AnyTitle", style: .plain, target: nil, action: nil)
}
}
I think something like this should do it. Try putting it in your custom NavigationController.
let backImage = UIImage(named: "backImage")
self.navigationController?.navigationBar.backIndicatorImage = backImage
self.navigationController?.navigationBar.backIndicatorTransitionMaskImage = backImage
EDIT
Sorry totally forgot about title. This should do it:
self.navigationController?.navigationBar.backItem?.title = "New title"
I want to be able to change the action of the back bar button item on a specific UIViewController in my navigation controller so that it pops to the root view controller. I've tried the following but they don't work:
let backButton = UIBarButtonItem(title: nil, style: .plain, target: self, action: #selector(back))
self.navigationItem.backBarButtonItem = backButton
and
self.navigationItem.backBarButtonItem?.action = #selector(back)
Any suggestions?
You should use self.navigationItem.leftBarButtonItem = backButton
Good luck
First of all backBarButtonItem action not works because you can only change back button title,take a look question about it here.
Solution
In ViewController from which you want to pop to root ViewController you need to set as a delegate of UINavigationControllerDelegate
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
and implement UINavigationControllerDelegate this method`
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
if viewController.isKind(of:PreviousViewController.self) {
navigationController.popToRootViewController(animated: animated)
}
}
If my answer not fit your needs you can check similar question here.
To keep the same look and feel of the back button but change the action, see the ViewWillDisappear answer to the question regarding, "Execute action when back bar button of UINavigationController is pressed" Execute action when back bar button of UINavigationController is pressed
This is your solution, just need to set target and selector, nothing more.
private func setNavBar() {
let item = navigationItem.backBarButtonItem
item?.target = self
item?.action = #selector(self.donePressed)
navigationItem.leftBarButtonItem = item
}
#objc private func donePressed() {
self.dismiss(animated: true, completion: nil)
}
I'd like to have a close button on each view controller that appears in the navigation stack. I've read here that I need to create an object that is a uinavigationdelegate, I think this object will have a method like didTapCloseButton?
Questions:
Should I create a protocol and make everything confirm to it, i.e.:
protocol CustomDelegate: UINavigationControllerDelegate {
func didTapCloseButton()
}
public class ViewController: CustomDelegate {
func didTapCloseButton() {
//not sure what goes in here?
}
}
How do I get the close button to show on the navigation bars of every view?
When the user clicks the close button, how do I get that to dismiss every view on that stack?
Thanks for your help!
Here a simple solution. Create UINavigationController subclass and override pushViewController method.
class NavigationController: UINavigationController {
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
super.pushViewController(viewController, animated: animated)
let closeBarButtonItem = UIBarButtonItem(
title: "Close",
style: .done,
target: self,
action: #selector(self.popViewController(animated:)))
viewController.navigationItem.rightBarButtonItem = closeBarButtonItem
}
}
Not sure if this is what you intended but you can do:
protocol CustomDelegate: UINavigationControllerDelegate {
func didTapCloseButton()
}
extension CustomDelegate where Self : UIViewController{
func didTapCloseButton(){
// write your default implementation for all classes
}
}
now for every UIViewController class you have you can just do :
class someViewController: CustomDelegate{
#IBAction buttonClicked (sender: UIButton){
didTapCloseButton()
}
}
I have a NavigationBar at the top of a TableView. It looks nice opening/closing the search.
However, if I click on a button in a cell and get directed to another page (with segue); and then use Back button to unwind, it seems like bugged.
()
So it looks like it is pressed and opened but it shouldn't have. It should be looked like the top picture instead (just UIBarButtonItem - search button)
I couldn't figure out the issue creating this problem.
Please note that < Back is created automatically and I didn't write any code to create it. Is there something I am doing wrong?
Update: Added some snippets...
First, created a different class for handling the search
class SearchBarViewController: UIViewController, UISearchBarDelegate {
var searchBar : UISearchBar?
var searchBarWrapper : UIView?
var searchBarButtonItem : UIBarButtonItem?
func constructSearchBar()
{
searchBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Search, target: self, action: "showSearchBar")
self.navigationItem.rightBarButtonItem = searchBarButtonItem
}
func showSearchBar() {
// styling & configuration
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
UIView.animateWithDuration(0.2, animations: {
self.searchBar?.resignFirstResponder()
self.searchBarWrapper?.alpha = 0
}, completion: { (success) -> Void in
self.searchBar = nil
self.searchBarWrapper = nil
self.navigationItem.rightBarButtonItem = self.searchBarButtonItem
})
}
}
And my ViewController:
class ViewController: SearchBarViewController {
override func viewDidLoad() {
super.viewDidLoad()
constructSearchBar()
}
}
Regarding to emrys57's answer, I tried adding viewWillAppear() in my ViewController but I couldn't make it work, as my cancel looks a little different:
override func viewWillAppear(animated: Bool) {
super.viewDidAppear(animated)
// Here, I couldn't figure out what to put because
// my searchBarCancelButtonClicked() needs searchBar and
// forces me to use (!) but then it says, it's optional..
}
The answer is...
override func viewWillAppear(animated: Bool) {
super.viewDidAppear(animated)
navigationItem.titleView = nil
constructSearchBar()
}
You have not posted code, so it's not entirely clear what's gone wrong. Using UISearchBar, I think you must be handling the buttons separately yourself, as opposed to using UISearchController. I think that you may not be clearing away the search bar when coming back from the second VC. This code clears out the search bar in viewWillAppear:
class ViewController: UIViewController {
var cancelButton: UIBarButtonItem?
var searchButton: UIBarButtonItem?
override func viewDidLoad() {
super.viewDidLoad()
cancelButton = UIBarButtonItem(barButtonSystemItem: .Cancel, target: self, action: Selector("searchCancelPressed:"))
searchButton = UIBarButtonItem(barButtonSystemItem: .Search, target: self, action: Selector("searchPressed:"))
}
override func viewWillAppear(animated: Bool) {
super.viewDidAppear(animated)
searchCancelPressed(nil)
}
func searchPressed(sender: AnyObject) {
navigationItem.titleView = UISearchBar()
navigationItem.rightBarButtonItem = cancelButton
}
func searchCancelPressed(sender: AnyObject?) {
navigationItem.titleView = nil
navigationItem.rightBarButtonItem = searchButton
}
}
and that is working nicely for me when I do a push from a button to the second VC and then hit back.
Following the edit to the original question, this code seems to work, although it may not be the most elegant way of constructing the answer:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
navigationItem.titleView = nil
searchBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Search, target: self, action: "showSearchBar")
navigationItem.rightBarButtonItem = searchBarButtonItem
}
The function constructSearchBar no longer needs to be called in viewDidLoad, and can be deleted.