I got the opposite issue from here.
By default in iOS7
, back swipe gesture of UINavigationController
's stack could pop the presented ViewController
. Now I just uniformed all the self.navigationItem.leftBarButtonItem
style for all the ViewControllers
.
Here is the code:
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:LOADIMAGE(@"back_button") style:UIBarButtonItemStylePlain target:self action:@selector(popCurrentViewController)];
after that, the navigationController.interactivePopGestureRecognizer
is disabled. How could I make the pop gesture enabled without removing the custom leftBarButtonItem
?
Thanks!
This question is related to
ios
objective-c
uinavigationcontroller
uinavigationbar
uinavigationitem
This is the best way to enable/ disable swipe to pop view controller in iOS 10, Swift 3 :
For First Screen [ Where you want to Disable Swipe gesture ] :
class SignUpViewController : UIViewController,UIGestureRecognizerDelegate {
//MARK: - View initializers
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
swipeToPop()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func swipeToPop() {
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true;
self.navigationController?.interactivePopGestureRecognizer?.delegate = self;
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == self.navigationController?.interactivePopGestureRecognizer {
return false
}
return true
} }
For middle screen [ Where you want to Enable Swipe gesture ] :
class FriendListViewController : UIViewController {
//MARK: - View initializers
override func viewDidLoad() {
super.viewDidLoad()
swipeToPop()
}
func swipeToPop() {
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true;
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil;
} }
For those who are still having trouble with this, try separating the two lines as below.
override func viewDidLoad() {
self.navigationController!.interactivePopGestureRecognizer!.delegate = self
...
override func viewWillAppear(_ animated: Bool) {
self.navigationController!.interactivePopGestureRecognizer!.isEnabled = true
...
Obviously, in my app,
interactivePopGestureRecognizer!.isEnabled
got reset to false
before the view was shown for some reason.
it works for me Swift 3:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
and in ViewDidLoad:
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
We're all working around some old bugs that haven't been fixed likely because it's "by design." I ran into the freezing problem @iwasrobbed described elsewhere when trying to nil the interactivePopGestureRecognizer
's delegate which seemed like it should've worked. If you want swipe behavior reconsider using backBarButtonItem
which you can customize.
I also ran into interactivePopGestureRecognizer
not working when the UINavigationBar
is hidden. If hiding the navigation bar is a concern for you, reconsider your design before implementing a workaround for a bug.
I did not need to add gestureRecognizer functions for it. It was enough for me to add following code blocks at viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
}
If you want this behaviour everywhere in your app and don't want to add anything to individual viewDidAppear
etc. then you should create a subclass
class QFNavigationController:UINavigationController, UIGestureRecognizerDelegate, UINavigationControllerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
delegate = self
}
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
super.pushViewController(viewController, animated: animated)
interactivePopGestureRecognizer?.isEnabled = false
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
interactivePopGestureRecognizer?.isEnabled = true
}
// IMPORTANT: without this if you attempt swipe on
// first view controller you may be unable to push the next one
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
}
Now, whenever you use QFNavigationController
you get the desired experience.
Most answers are pertaining to doing it on code. But I'll give you one that works on Storyboard. Yes! You read it right.
Click on main UINavigationController
and navigate to it's Identity Inspector
tab.
Under User Defined Runtime Attributes
, set a single runtime property called interactivePopGestureRecognizer.enabled
to true
. Or graphically, you'd have to enable the checkbox as shown in the image below.
That's it. You're good to go. Your back gesture will work as if it was there all along.
Setting a custom back button disable the swipe back feature.
The best thing to do to keep it is to subclass UINavigationViewController
and set itself as the interactivePopGestureRecognizer
delegate; then you can return YES from gestureRecognizerShouldBegin
to allow the swipe back.
For example, this is done in AHKNavigationController
And a Swift version here: https://stackoverflow.com/a/43433530/308315
This answer, but with storyboard support.
class SwipeNavigationController: UINavigationController {
// MARK: - Lifecycle
override init(rootViewController: UIViewController) {
super.init(rootViewController: rootViewController)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
private func setup() {
delegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
// This needs to be in here, not in init
interactivePopGestureRecognizer?.delegate = self
}
deinit {
delegate = nil
interactivePopGestureRecognizer?.delegate = nil
}
// MARK: - Overrides
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
duringPushAnimation = true
super.pushViewController(viewController, animated: animated)
}
// MARK: - Private Properties
fileprivate var duringPushAnimation = false
}
You need to handle two scenarios:
If you just need a base class you can use, here's a Swift 3 version:
import UIKit
final class SwipeNavigationController: UINavigationController {
// MARK: - Lifecycle
override init(rootViewController: UIViewController) {
super.init(rootViewController: rootViewController)
delegate = self
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
delegate = self
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
delegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
// This needs to be in here, not in init
interactivePopGestureRecognizer?.delegate = self
}
deinit {
delegate = nil
interactivePopGestureRecognizer?.delegate = nil
}
// MARK: - Overrides
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
duringPushAnimation = true
super.pushViewController(viewController, animated: animated)
}
// MARK: - Private Properties
fileprivate var duringPushAnimation = false
}
// MARK: - UINavigationControllerDelegate
extension SwipeNavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
guard let swipeNavigationController = navigationController as? SwipeNavigationController else { return }
swipeNavigationController.duringPushAnimation = false
}
}
// MARK: - UIGestureRecognizerDelegate
extension SwipeNavigationController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard gestureRecognizer == interactivePopGestureRecognizer else {
return true // default value
}
// Disable pop gesture in two situations:
// 1) when the pop animation is in progress
// 2) when user swipes quickly a couple of times and animations don't have time to be performed
return viewControllers.count > 1 && duringPushAnimation == false
}
}
If you end up needing to act as a UINavigationControllerDelegate
in another class, you can write a delegate forwarder similar to this answer.
Adapted from source in Objective-C: https://github.com/fastred/AHKNavigationController
In Swift you can do the following code
import UIKit
extension UINavigationController: UIGestureRecognizerDelegate {
open override func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
}
Above code helps in swift left to go back to previous controller like Facebook, Twitter.
Swift 3:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return (otherGestureRecognizer is UIScreenEdgePanGestureRecognizer)
}
It works for me when I set the delegate
self.navigationController.interactivePopGestureRecognizer.delegate = self;
and then implement
Swift
extension MyViewController:UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Objective-C
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Source: Stackoverflow.com