Navigation Bar's Default behaviour to go back is not working - ios

I have a view controller where I need to add a custom back button. So I have added a custom backButtonItem. But After adding custom back button the default behavior of my view controller to go back by swipe stops working as expected.
If I remove the custom back button from the view controller, the behavior of view controller is as expected but as soon as I add custom back button the default behavior stops.
I have added the custom back button like this
self.navigationItem.leftBarButtonItem = getCustomBackBarButtonItem(viewController: self)
I have tried to use backBarButtonItem instead of leftBarButtonItem, but by doing that the custom back button doesn't appear and the view controller's behavior is as expected.
If I remove the above code the behavior of the view controller is as expected and it smoothly goes back by swipe.

Be sure to build the UIBarButtonItem doing something like this:
let customBack = UIBarButtonItem(title: "Back", style:.done, target:self, action: #selector(self.letsGoBack))
and then implement the pop back function with:
#objc func letsGoBack() {
self.navigationController?.popViewController(animated: true)
}
so at the end it's just:
self.navigationItem.leftBarButtonItem = customBack
if you want to keep the swipe back gesture, you might subclass your navigation controller doing so:
class YourNavigationController: UINavigationController, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return self.viewControllers.count > 1
}
}

Instead of setting delegate to self you can simply add this line:
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
So it won't go to any of interactive PopGestureRecognizer's delegate method and the navigation Controller behavior will be as per your expectation. This is the small workaround to achieve the expected behavior.

To navigate back to previous viewController
Initialize UIBarButton
var backBtn:UIBarButtonItem!
add UIBarButton to NavigationBar
backBtn = UIBarButtonItem(title: "Go-Back" , style: .plain, target: self, action: #selector(self.backBtnClicked(_:)))
self.navigationItem.leftBarButtonItem = backBtn
add this function into your class
func backBtnClicked(_ sender:UIBarButtonItem) {
if let redirect = self.navigationController?.popViewController(animated: true) {
// If you open this viewController by using pushViewController this will called
} else {
self.dismiss(animated: true, completion: nil)
// If you open this viewController by using present this will called
}
}
Hope this will help you

Related

Adding to existing UINavigationController in iOS / Swift

I have an existing navigation controller delegate that places a menu button on each view controller in the app.
class MyNavigationControllerDelegate: NSObject, UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
let navItem = viewController.navigationItem
let menuBtn = MyCustomMenuButton()
...
navItem.setRightBarButton(menuBtn, animated: false)
}
This works great...I get a menu button in the nav bar for each view. But for some views, I'd like to add another button on the right next to the menu button, so I added this:
class CustomViewController: UIViewController, UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let newButton = UIBarButtonItem(title: "(+)", style: .plain, target: nil, action: nil)
self.navigationItem.rightBarButtonItem = newButton
}
}
But this has no effect. The menu button is still there but the new button is not added. How should this be done then?
But this has no effect
Yes, it does. But you don't have time to see the effect. You are saying the same thing twice, because setRightBarButtonItem is the same as rightBarButtonItem =... So whichever one you say second, that is the one that is ultimately obeyed; either way, it rips the existing right bar button item out and replaces it with the other one.
If the goal is to have multiple right bar button items, that is what the rightBarButtonItems is for (notice the plural). You can call setRightBarButtonItems instead (again, notice the plural). Looking to see whether there is already a right bar button item and taking that into account is up to you, of course. There is no magical append method.

Navigation Controller and Tab Bar Controller

Very stuck on an issue
I have a tab bar controller within a navigation controller
The first tab has a calendar on it (which is basically a collection view)
I am trying to make a rightbarbuttonitem to scroll to todays date
I can only seem to create the button within the tabbarcontroller
The function that I call then calls one in the CalendarViewController
but it doesn't seem to work
What is the correct way to implement a bar button item within a tabbarcontroller?
In the tabbarcontroller I have...
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: self, action: #selector(goToToday))
#objc func goToToday() {
print("pressed")
CalendarViewController().goToToday()
}
In the CalendarViewController I have...
#objc func goToToday(_ animate: Bool = true) {
print("tapped")
calendarView.scrollToDate(Date(),animateScroll: animate)
calendarView.selectDates([Date()])
title = "Test"
}
The function works if I call it from a button in CalendarViewController. And I get both print commands when called from the navigation controller but that's all
You should call gotToToday on the instance you're using as the first tab and not on a new instance as you've done here. Here's what you need to do in gotToToday in TabBarController:
#objc func goToToday() {
print("pressed")
if let calendarViewController = viewControllers[0] as? CalendarViewController {
calendarViewController.goToToday()
}
}

Issue with Modal present and dismiss

I have a ViewController that has 20 points padding around the edge of the main content. This is clear colour to be transparent.
I then use the following to open it as a Modal.
noteDetailController.modalPresentationStyle = .overCurrentContext
present(noteDetailController, animated: false, completion: nil)
My idea was that I would set a UITapGestureRecognizer to allow the user to tap anywhere in the padding area to dismiss the Modal.
The following is hit by this UITapGestureRecognizer, inside the Modal ViewController
self.parentController?.dismiss(animated: false, completion: nil)
I'm having issues where the ViewController underneath the Modal seems to be responding the pressing the screen and there's a major lag in loading and dismissing Modals.
Does this logic seem correct, am I doing things in a way that you would expect to work? It almost seems like the present and dismiss start happening out of sync and several Modals are loaded.
Thanks for your time.
Your view controller underneath should not be getting touch events while another view controller is placed above it.
Make a segue to the modal controller as Kind: Present Modally, Presentation: Over Current Context, and call the segue as performSegue(withIdentifier: "showModal", sender: nil)
Make sure the view of the modal view controller has checked "User Interaction Enabled" and has a clear color background.
Connect the container view of the modal to the modal view controller with an IBOutlet, add a UITapGestureRecognizer to the modal's view (padding) and implement a UIGestureRecognizerDelegate method to detect that the tap was indeed only in padding and not in the container view itself:
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(hide(gr:)))
tap.delegate = self
view.addGestureRecognizer(tap)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if let v = touch.view, v.isDescendant(of: containerView) {
return false
}
return true
}
#objc func hide(gr: UITapGestureRecognizer) {
dismiss(animated: true)
}

Button in navigation bar

How do I add a button to navigation bar that appears only in a particular view controller? And also how to add different buttons to different view controller that is embedded in navigation controller.
You don't have to add buttons to navigation bar, rather you could add bar button items. If you want you can customise bar button item to meet your requirements like setting a custom image as suggested by #Pushpendra.
Programatically:
Your View Controller is responsible for providing bar button item(s) for it's navigation bar. Create bar button items inside each of your view controller's viewDidLoad() or in init() if you have overridden it.
Storyboards:
It's pretty handy to drag n drop bar button items from storyboard editor and have the outlets setup so that you can change the behaviour later.
If you are precisely looking to control the behaviour, then you can add or remove a bar button as and when needed depending on your conditional requirements.
class FooViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
/// Assuming this view controller presented/pushed with navigation controller
let buttonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(FooViewController.buttonItemAction(sender:)))
self.navigationItem.leftBarButtonItem = buttonItem
}
#IBAction func buttonItemAction(sender: UIBarButtonItem) {
/// some action
}
}
class BarViewController: UIViewController {
var buttonItem: UIBarButtonItem?
var showButtonItem = false
override func viewDidLoad() {
super.viewDidLoad()
/// Assuming this view controller presented/pushed with navigation controller
buttonItem = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: self, action: #selector(BarViewController.buttonItemAction(sender:)))
self.navigationItem.leftBarButtonItem = buttonItem
}
#IBAction func buttonItemAction(sender: UIBarButtonItem) {
/// some action
}
#IBAction func toggleButtonItem(sender: UIButton) {
self.navigationItem.rightBarButtonItem = (self.showButtonItem == true) ? self.buttonItem : nil
}
}
If this is what you are not looking for then update your question along with the code that you have tried.
try this :-
extension UIViewController {
func addButton(img:String){
let imgIcon = UIImage(named: img)
let btn = UIBarButtonItem(image: imgIcon, style: .plain, target:
self, action: #selector(self.btnAction))
// if you want to set right button then add right bar button item
self.navigationItem.leftBarButtonItem = btn
}
func btnAction(){
}
func removeButton() {
self.navigationItem.leftBarButtonItem = nil
}
}
when you want to add button
in viewDidLoad :-
self.addButton(img:"imageName")
also if you want to perform separate action on all view controller then
call this on controller
override func btnAction() {
}
to remove :-
self.removeButton()
if you want to add different button on different controller add the button in viewDidLoad and remove in viewWillDisappear
Drag a bar button item in your navigation bar for the specific view controller in your storyboard
The best solution is
Create Viewcontroller inherit from UIViewController or UITableViewController and write a method for setupNavigationButton or buttonText or
(Better to add an action method on a super controller also)
eg:
-(void) setupNavigationButton {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:#"button1" style:UIBarButtonItemStylePlain target:self action:#selector(buttonActin:)];
}
and call this method on viewDidLoad on the created controller
Next when you create your own controller add this viewcontroler as your parent view controller. So it will automatically add that view.
(You want to write an action on your controller)
Using above method, it saves only one line. But you can save more lines. Let's assume you want to change only text and action on that button.
If it is only you need to change text, write a method to return button text
(Following line for your baseviewcontroller)
-(NSString *) rightButtonText {
return #"";
}
and add following codes for baseviewcontroller
-(void)setNavigationControler {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:[self rightButtonText] style:UIBarButtonItemStylePlain target:self action:#selector(buttonActin:)];
}
and call it on baseviewcontroller
Now your every view controller, you need to override rightButtonText and create selector method and do that action.
Assume you don't need to add that button for some viewcontroler. Then you have a few options
1) Remove baseviewcotroler inheritance from your viewcontroller
2) After call supper viewDidload add following code
self.navigationItem.rightBarButtonItem =
3) Override setNavigationControler method on your controller with the blank method body.
-(void)setNavigationControler {}

Swift how to preserve navigation bar button items after push

In my root view controller I set an array of bar button items with images and assign them to the right bar button.
When I push the next view controller my navigation bar resets and only displays a back button.
Any way to preserve the navigation bar as it was set on the root view controller so it will display on all pages?
As user1046037 has said you can set the item buttons while you are preparing the segue.
Example:
let helpViewController = HelpViewController(nibName: "HelpViewController", bundle: nil)
let someLeftButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.refresh, target: self, action: "someAction")
helpViewController.navigationItem.leftBarButtonItem = someLeftButton
helpViewController.navigationItem.leftItemsSupplementBackButton = true
navigationController?.pushViewController(helpViewController, animated: true)
This one is to preserve the left button item and the back one.
helpViewController.navigationItem.leftItemsSupplementBackButton = true
If you are going to use the same button in several Viewcontrollers you can create a BaseViewController setting up the button and his behaviors.
class AHBaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
configureNavigationBar()
// Configure Common status bar if you want too.
}
func configureNavigationBar() {
let someLeftButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.refresh, target: self, action: "someAction")
navigationItem.leftBarButtonItem = someLeftButton
navigationItem.leftItemsSupplementBackButton = true
}
}
Then just inherit it, in the viewControllers that you want to show the button(s).
class HelpViewController: AHBaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
When you segue between view controllers using Push, you're going to get the default "horizontal slide" navigation animation which. Try "present Modally" or "present as popover".

Resources