I'm testing an app and I discovered that when double tapping the back button, it automatically pops to root view controller of the navigation controller. There is no code anywhere that does this explicitly and I was wondering if this is the default behavior of the back button in a navigation controller.
I've tested the same thing in another app and the issue wasn't there, so the only thing that explains this is that something else is triggering that popToRoot and I can't figure out what.
Sorry, but I cannot post the code here. Just want your opinion on this.
Thanks in advance!
As I commented above at Glenns answer, dispatching the popViewController into the main thread might be a problem because it will be executed also when a double tap is recognized. Maybe this is a slightly more "stable" solution:
private func setupBackButton() {
let myBackButton = UIButton(type: .custom) // or whatever
// setup title, image etc.
let myCustomBackButtonItem = UIBarButtonItem(customView: myBackButton)
self.navigationItem.leftBarButtonItem = myCustomBackButtonItem
// Create gesture recognizers for single and double tap:
let singleRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.singleTap))
singleRecognizer.numberOfTapsRequired = 1
let doubleRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.doubleTap))
doubleRecognizer.numberOfTapsRequired = 2
// single tap should only fire if double tap is not recognized
singleRecognizer.require(toFail: doubleRecognizer)
myBackButton.addGestureRecognizer(singleRecognizer)
myBackButton.addGestureRecognizer(doubleRecognizer)
}
#objc private func singleTap() {
self.navigationController?.popViewController(animated: true)
}
#objc private func doubleTap() {
self.navigationController?.popToRootViewController(animated: true)
}
When I saw this question, I implemented the idea onto my current project, and it worked, thanks for this. So an idea to do this is have a custom back button. I could post the whole code to this but that would make it a spoonfeeding. Anyways, here goes the steps:
Make a UIButton and make it your custom back button UIBarButtonItem in your navigationController's navigationBar if you ever use one.
The selector would be obviously like this, don't mind much the code inside, cause this is taken out of my project, but you get the idea:
func back() {
if let navCon = self.navigationController {
navCon.popViewController(animated: true)
}
}
And for the double tap, you'll need a tapGesture for that. I have this one declared like so:
private lazy var doubleTapGesture: UITapGestureRecognizer = {
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.doubleTap))
gesture.numberOfTapsRequired = 2
return gesture
}()
and add that to your button. Selector would be like this:
#objc private func doubleTap() {
self.navigationController?.popToRootViewController(animated: true)
}
VOILA! Works smoothly. Easy, right? I hope this helps!
Thank you for all your answers but my issue was that I DIDN'T want that behavior, my post was misinterpreted and I'm sorry for that.
Otherwise, I solved myself the problem. There was a subview added to navigation controller's navigation bar which was hidden up, below the bar. And somehow, that subView had interaction enabled even if it was below. So I disabled interaction when hidden. That's all.
Related
short: I don´t understand how to programmatically add the default back button.
long: I have been asked to write an iOS app, without any previous experience I decided
to follow the advices and code given by Nicola Zaghini.
In the code given along with the article, I really don´t understand
where does the back button come from.
The app has three screens
one to choose a city (folder WeatherLocation)
one that displays all
cities that have been already choosen (folder WeatherList)
one that
displays the weather for a city clicked in the list (folder
WeatherDetail)
There is + button for WeatherLocation:
This button is added in the code but I can not find where
and how is coded the back button in WeatherDetails (see above), and how is coded the action to do when one click the back button.
I search the web and found how to set a button in the NavigationBar:
let leftBarButtonItem: UIBarButtonItem = {
let barButtonItem = UIBarButtonItem(title: "Left Item", style: .plain, target: self, action: nil)
barButtonItem.tintColor = UIColor.red
return barButtonItem
}()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.leftBarButtonItem = leftBarButtonItem
}
I also found that there is a backBarButtonItem
but I could not find how to properly use this property.
Moreover in the code of Nicola Zaghini there is nothing like backBarButtonItem to enable the
back button, neither in the xib and storyboard.
Can someone give me some hints about on how to set the back button ?
Thanks a lot!!
In the code of Nicola Zaghini the secret to have a default back button resides (for example) in the function navigateToWeatherDetail in the class WeatherListDefaultRouter where the new ViewController is pushed on a NavigationController:
func navigateToWeatherDetail(withLocation location: Location) {
if let weatherDetailVC = self.weatherDetailBuilder()?.buildWeatherDetailModule(withLocation: location) {
self.viewController?.navigationController?.pushViewController(weatherDetailVC, animated: true)
}
}
Mine screen contain UITabBar (tabs) and childViewController (content).
When I switch to another tab, I replace current child with next code:
private func replaceChild(_ old: UIViewController?, with new: UIViewController, in container: UIView) {
old?.view.removeFromSuperview()
old?.removeFromParentViewController()
container.addSubviewWithConstraints(new.view)
self.addChildViewController(new)
self.replaceNavBar(with: new)
}
Also I try to steal barButtons from child and put them in VC:
private func replaceNavBar(with new: UIViewController) {
self.title = new.title
self.navigationItem.leftBarButtonItems = new.navigationItem.leftBarButtonItems
self.navigationItem.rightBarButtonItems = new.navigationItem.rightBarButtonItems
}
Console check rightBarButtonItem setting for first tab (right button +):
Before:
oldVC: nil
newVC: <UIBarButtonItem: 0x7fb5f742bb50> target=0x7fb5f907c800 action=createNewMessage systemItem=Add
After:
oldVC: <UIBarButtonItem: 0x7fb5f742bb50> target=0x7fb5f907c800 action=createNewMessage systemItem=Add
Also if I set nil (for tabs, where no barButtons needed) - in debugger all show's OK and no error message happens.
But on UI, all buttons stays the same (if first tab was with buttons - buttons displayed and actions triggered. If it was no buttons -> no buttons). I store childs in array in main VC - so, no reinit happens
I'm pretty sure, that it's possible, because I've implement this flow some time ago and it works correct (put's child buttons to main VC, but stores target and selector), but I lost that code
Question: How I can steal barButtonItems from child VC and put them in parent VC navItem?
Upd
I checked barButton setting with dumb code and it worked, but not on UI: breakpoints inside if and else blocks called by turns, but no button was added to navItem:
if let _ = self.navigationItem.rightBarButtonItem {
self.navigationItem.rightBarButtonItem = nil
} else {
let addButton = UIBarButtonItem.init(barButtonSystemItem: .add, target: nil, action: nil)
addButton.tintColor = .white
self.navigationItem.rightBarButtonItem = addButton
}
Also I tried version with self.navigationController?.navigationItem.rightBarButtonItem
Swift 4.1, Xcode 9.4.1
Solution found:
I forgot about very important thing - my VC is child for another one VC.
So I should perform not the self.navigationItem.rightBarButtonItem = ... but self.parent?.navigationItem.rightBarButtonItem = ...
I'm new to IOS and learning the IDE at the moment. I was wondering if its possible to link an imageView, when clicked, to a View Controller
Sure. First, in viewDidLoad of your UIViewController make it tappable:
imageView.isUserInteractionEnabled = true
Then assign the gesture recognizer:
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageTap)))
Once the action is triggered, perform a transition to a new VC:
// Func in your UIViewController
#objc func imageTap() {
// present modally
self.present(YourNewViewController())
// or push to the navigation stack
self.navigationController?.push(YourNewViewController())
// or perform segue if you use storyboards
self.preformSegue(...)
}
Yes. It is possible. You'll need to add a tap gesture recogniser to image view and in the function perform segue.
SWIFT 4
let singleTap = UITapGestureRecognizer(target: self,action:Selector(“imageTapped”))
yourImageView.isUserInteractionEnabled = true
yourImageView.addGestureRecognizer(singleTap)
Function to handle Tap:
#objc func imageTapped() {
performSegue(withIdentifier: "yourNextScreen", sender: "")
}
Yes. You can bind a tap gesture recognizer to your imageview. You can follow this link here on a previous answer on how to do it programmatically
You could do it completely in the interface builder by putting a UIButton, with a transparent background and no text, over the ImageView and then control drag the button to the viewcontroller you want to trigger a segue on touch.
Of course it is. You should add a UITapGestureRecognizer to the UIImageView and add a selector to trigger the pushing a new viewcontroller method.
For example;
#IBOutlet weak var imageView: UIImageView! {
didSet {
let imageTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
imageView.addGestureRecognizer(imageTapGestureRecognizer)
imageView.isUserInteractionEnabled = true
}
}
func imageTapped() {
//navigate to another view controller
}
add gesture and if you want to animate it then see this pod
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
I have developed an app that makes use of the iOS8 feature to show or hide the navigation bar on a tap of the view.
However, the main view contains a UIButton which also act upon taps. The problem is that both 'objects' are receiving the tap and if I tap the button, the navigation bar toggles its visibility.
I can get to the barHideOnTapGestureRecognizer via the navigation controller but not really sure what can be done with it to stop it responding if a button is tapped.
Is there a way (apart from switching off or changing to 'Swipe to Hide') to subdue the navigation bar's appearance/disappearance when a button is pressed?
Don't use the standard barHideOnTapGestureRecognizer. Fortunately, it's not hard to roll your own:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let gestureRecognizer = UITapGestureRecognizer(target: self, action: "toggleBarsOnTap:")
self.view.addGestureRecognizer(gestureRecognizer)
}
func toggleBarsOnTap(sender: AnyObject?) {
let hidden = !self.navigationBarHidden
self.setNavigationBarHidden(hidden, animated: true)
self.setToolbarHidden(hidden, animated: true)
}
Taps on the view will show/hide the bars, and taps on controls (subviews of the view) will not.
[self.navigationController setNavigationBarHidden:YES];