How to disable back gesture in iOS 7 for only one view - ios7

I am trying to disable the back gesture for my view controller using the following set of code.
In FirstViewController.m, I'm setting the delegate of interactivePopGestureRecognizer
- (void) viewWillLoad {
// Other stuff..
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
And then implementing the <UIGestureRecognizerDelegate> method and returning NO.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return NO;
}
And in dealloc I'm setting the delegate to nil. (I have read somewhere that in iOS 7, you have to manually set the delegates to nil)
- (void)dealloc {
self.navigationController.delegate = nil;
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
This works in the FirstViewController. But when I push SecondViewController to this, the gesture does not work on that either. How can I disable the gesture in FirstViewController only?
Also when I pop FirstViewController to go to RootViewController and then try to push FirstViewController again, I get the object deallocated error :
[FirstViewController gestureRecognizer:shouldReceiveTouch:]: message sent to deallocated instance 0x14ed0280
Why else do I need to do other than setting the delegates to nil? Or am I setting it in the wrong place?

Try the below untested code in your FirstViewController :
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
}

I originally put these answers into a comment below the accepted answer, but I feel this needs to be said as an answer to get more visibility.
More often than not, you will find that the accepted answer does not work. This is because viewWillAppear: can be called before the view is added to a navigation controller's view hierarchy, and so self.navigationController is going to be nil. Because of this, the interactivePopGestureRecognizer may not be disabled in some cases. You're better off calling it in viewDidAppear: instead.
Here's code that will work (assuming your view controller is correctly added to a navigation controller's view hierarchy):
Objective-C
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[[self navigationController] interactivePopGestureRecognizer] setEnabled:NO];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[[self navigationController] interactivePopGestureRecognizer] setEnabled:YES];
}
Swift
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.interactivePopGestureRecognizer?.isEnabled = true
}

I tried the all above but they did not work for me.So i tried this and it works for me on both IOS7 and IOS8.
Just make sure that your view controller implements this protocol i.e UIGestureRecognizerDelegate
and write the code given below.
-(void)viewWillAppear : (BOOL) animated {
[super viewWillAppear : animated];
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled =
NO;
self.navigationController.interactivePopGestureRecognizer.delegate =
self;
}
}
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer isEqual:self.navigationController.interactivePopGestureRecognizer]) {
return NO;
} else {
return YES;
}
}

I found out setting the gesture to disabled only doesn't always work. It does work, but for me it only did after I once used the backgesture. Second time it wouldn't trigger the backgesture. Furthermore, as John Rogers said, it's import to use the viewDidAppear and viewWillAppear as the navigationController else would be nil.
Fix for me was to delegate the gesture and implement the shouldbegin method to return NO:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Disable iOS 7 back gesture
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Enable iOS 7 back gesture
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
return NO;
}

This just worked for me in xCode 7:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.navigationController!.interactivePopGestureRecognizer!.enabled = false
}
override func viewWillDisappear(animated: Bool) {
super.viewDidDisappear(animated)
self.navigationController!.interactivePopGestureRecognizer!.enabled = true
}

For only one view, I don't know the way... But I use the next code to disable fully the swipe gesture:
in your AppDelegate.m
if ([[UIDevice currentDevice].systemVersion floatValue] >= 7){
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}

Related

How to disable the magnifier on a UITextField in Objective C Programmatically? [duplicate]

In UITextview when touch is pressed for the longer time magnifying glass shows up. How can i disable it.
Finally this issue is also resolved
Here is the code for reference in case anyone needs
in the m file of subclassed UITextview added code
-(void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
//Prevent zooming but not panning
if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
{
gestureRecognizer.enabled = NO;
}
[super addGestureRecognizer:gestureRecognizer];
return;
}
It works.
This works for me
#implementation CustomTextView
- (NSArray *)selectionRectsForRange:(UITextRange *)range
{
self.selectedTextRange = nil;
return nil;
}
- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
gestureRecognizer.delegate = self;
[super addGestureRecognizer:gestureRecognizer];
return;
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return NO;
}
- (CGRect)caretRectForPosition:(UITextPosition *)position
{
return [super caretRectForPosition:self.endOfDocument];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if (([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] && !gestureRecognizer.delaysTouchesEnded))
{
return NO;
}
else
return YES;
}
Swift 4 version of #user1120133's answer:
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
//Prevent long press to show the magnifying glass
if gestureRecognizer is UILongPressGestureRecognizer {
gestureRecognizer.isEnabled = false
}
super.addGestureRecognizer(gestureRecognizer)
}
Xamarin.iOS:
Create custom UITextView
Override GestureRecognizerShouldBegin method on your UITextView
public override bool GestureRecognizerShouldBegin(UIGestureRecognizer gestureRecognizer)
{
if (gestureRecognizer is UILongPressGestureRecognizer ||
gestureRecognizer.Name != "UITextInteractionNameLinkTap")
{
return false;
}
return true;
}
#Irina's answer works partially (Try a tap followed by a long press and you will have a magnifying overlay) for iOS 9.x and crashes on iOS 10 with the following:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'You cannot change the delegate
of the UIViewControllerPreviewing failure relationship gesture
recognizer'
The following code works both for iOS 9.x and 10.x in every combination of tap and/or long gestures I could think of.
Note I don't guarantee that it will be accepted by Apple's review.
#implementation CustomTextView
- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
NSArray *allowedGestures = #[ #"UILongPressGestureRecognizer", #"UIScrollViewDelayedTouchesBeganGestureRecognizer", #"UIScrollViewPanGestureRecognizer" ];
if (![allowedGestures containsObject:NSStringFromClass([gestureRecognizer class])])
{
return;
}
if (([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] && !gestureRecognizer.delaysTouchesEnded))
{
return;
}
[super addGestureRecognizer:gestureRecognizer];
}
#end
We need UIScrollViewDelayedTouchesBeganGestureRecognizer and UIScrollViewPanGestureRecognizer in order to keep the UITextView ability to scroll. Both classes are part of private API so use that at your own risk.

Only one view controller orientation issue when going back to pervious view controller in ios 7

In my app we need only one view controller would be in all orientation mode other view controllers will be portrait mode only.
I am using below code and it's working perfectly but when coming back to pervious view controller it's not rotating in portrait mode when I am coming from Landscape mode.
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
// Get topmost/visible view controller
UIViewController *currentViewController = [self topViewController];
// Check whether it implements a dummy methods called canRotate
if ([currentViewController respondsToSelector:#selector(canRotate)]) {
// Unlock landscape view orientations for this view controller
return UIInterfaceOrientationMaskAllButUpsideDown;
}
// Only allow portrait (standard behaviour)
return UIInterfaceOrientationMaskPortrait;
}
- (UIViewController*)topViewController {
return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
if ([rootViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController* tabBarController = (UITabBarController*)rootViewController;
return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
} else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController*)rootViewController;
return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
} else if (rootViewController.presentedViewController) {
UIViewController* presentedViewController = rootViewController.presentedViewController;
return [self topViewControllerWithRootViewController:presentedViewController];
} else {
return rootViewController;
}
}
I've been working against the same issue. The orientation can be forced back to its normal configuration by adding an adjustment in viewWillAppear of the controller you are going back to:
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationPortrait];
[[UIDevice currentDevice] setValue:value forKey:#"orientation"];
}

viewWillAppear not called in BCTabBarController

I've large project where customer want's to customize tabbar. I've choose BCTabBarController to replace UITabbarController. After few fixes it works fine but after testing I found one bug:
ViewWillAppear, ViewDidAppear, ViewWillDisappear ViewDidDisappear methods not called in selectded view controller and not called into BCTabBarController.
This problem appears after BCTabBarController show modal controller from instance of BCTabBarController class.
I've posted issue to github repo of briancolins, but still have no answer.
Here some code where I calling present modal view controller:
- (void) presentProperlyModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated
{
if ([[self controllerToPresentModalFrom] respondsToSelector:#selector(presentViewController:animated:completion:)]) // For iOS 5
{
[[self controllerToPresentModalFrom] presentViewController:modalViewController animated:animated completion:^(){}];
}
else
{
[[self controllerToPresentModalFrom] presentModalViewController:modalViewController animated:animated];
}
}
-(void) dismissProperlyModalViewControllerAnimated:(BOOL)animated
{
if ([self respondsToSelector:#selector(dismissViewControllerAnimated:completion:)]) {
[self dismissViewControllerAnimated:animated completion:^(){}];
}
else
{
[self dismissModalViewControllerAnimated:YES];
}
}
UPDATE: this issue not reproduced in iOS5 but present at iOS 4.3
As you indicated. iOS 5 forwards the messages, where previous versions do not. Here's how I handle a similar situation:
- (BOOL)needsMessageForwarding:(UIViewController *)vc {
if ( [vc isKindOfClass:[UINavigationController class]] == NO)
return YES;
NSString *ver = [UIDevice currentDevice].systemVersion;
if ( [ver characterAtIndex:0 < '5'] )
return YES;
return NO;
}
- (void) viewWillAppear:(BOOL)animated {
...
if ( [self needsMessageForwarding:modalViewController] )
[modalViewController viewWillAppear:animated];
...
}
// repeat pattern in the other viewWill... viewDid... functions.
In my situation I have a list of view controllers that could be visible, so I manage which view controller is visible and forward the message to it.

UIGestureRecognizer blocks subview for handling touch events

I'm trying to figure out how this is done the right way. I've tried to depict the situation:
I'm adding a UITableView as a subview of a UIView. The UIView responds to a tap- and pinchGestureRecognizer, but when doing so, the tableview stops reacting to those two gestures (it still reacts to swipes).
I've made it work with the following code, but it's obviously not a nice solution and I'm sure there is a better way. This is put in the UIView (the superview):
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if([super hitTest:point withEvent:event] == self) {
for (id gesture in self.gestureRecognizers) {
[gesture setEnabled:YES];
}
return self;
}
for (id gesture in self.gestureRecognizers) {
[gesture setEnabled:NO];
}
return [self.subviews lastObject];
}
I had a very similar problem and found my solution in this SO question. In summary, set yourself as the delegate for your UIGestureRecognizer and then check the targeted view before allowing your recognizer to process the touch. The relevant delegate method is:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch
The blocking of touch events to subviews is the default behaviour. You can change this behaviour:
UITapGestureRecognizer *r = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(agentPickerTapped:)];
r.cancelsTouchesInView = NO;
[agentPicker addGestureRecognizer:r];
I was displaying a dropdown subview that had its own tableview. As a result, the touch.view would sometimes return classes like UITableViewCell. I had to step through the superclass(es) to ensure it was the subclass I thought it was:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
UIView *view = touch.view;
while (view.class != UIView.class) {
// Check if superclass is of type dropdown
if (view.class == dropDown.class) { // dropDown is an ivar; replace with your own
NSLog(#"Is of type dropdown; returning NO");
return NO;
} else {
view = view.superview;
}
}
return YES;
}
Building on #Pin Shih Wang answer. We ignore all taps other than those on the view containing the tap gesture recognizer. All taps are forwarded to the view hierarchy as normal as we've set tapGestureRecognizer.cancelsTouchesInView = false. Here is the code in Swift3/4:
func ensureBackgroundTapDismissesKeyboard() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
tapGestureRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(tapGestureRecognizer)
}
#objc func handleTap(recognizer: UIGestureRecognizer) {
let location = recognizer.location(in: self.view)
let hitTestView = self.view.hitTest(location, with: UIEvent())
if hitTestView?.gestureRecognizers?.contains(recognizer) == .some(true) {
// I dismiss the keyboard on a tap on the scroll view
// REPLACE with own logic
self.view.endEditing(true)
}
}
One possibility is to subclass your gesture recognizer (if you haven't already) and override -touchesBegan:withEvent: such that it determines whether each touch began in an excluded subview and calls -ignoreTouch:forEvent: for that touch if it did.
Obviously, you'll also need to add a property to keep track of the excluded subview, or perhaps better, an array of excluded subviews.
It is possible to do without inherit any class.
you can check gestureRecognizers in gesture's callback selector
if view.gestureRecognizers not contains your gestureRecognizer,just ignore it
for example
- (void)viewDidLoad
{
UITapGestureRecognizer *singleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
singleTapGesture.numberOfTapsRequired = 1;
}
check view.gestureRecognizers here
- (void)handleSingleTap:(UIGestureRecognizer *)gestureRecognizer
{
UIEvent *event = [[UIEvent alloc] init];
CGPoint location = [gestureRecognizer locationInView:self.view];
//check actually view you hit via hitTest
UIView *view = [self.view hitTest:location withEvent:event];
if ([view.gestureRecognizers containsObject:gestureRecognizer]) {
//your UIView
//do something
}
else {
//your UITableView or some thing else...
//ignore
}
}
I created a UIGestureRecognizer subclass designed for blocking all gesture recognizers attached to a superviews of a specific view.
It's part of my WEPopover project. You can find it here.
implement a delegate for all the recognizers of the parentView and put the gestureRecognizer method in the delegate that is responsible for simultaneous triggering of recognizers:
func gestureRecognizer(UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer:UIGestureRecognizer) -> Bool {
if (otherGestureRecognizer.view.isDescendantOfView(gestureRecognizer.view)) {
return true
} else {
return false
}
}
U can use the fail methods if u want to make the children be triggered but not the parent recognizers:
https://developer.apple.com/reference/uikit/uigesturerecognizerdelegate
I was also doing a popover and this is how I did it
func didTap(sender: UITapGestureRecognizer) {
let tapLocation = sender.locationInView(tableView)
if let _ = tableView.indexPathForRowAtPoint(tapLocation) {
sender.cancelsTouchesInView = false
}
else {
delegate?.menuDimissed()
}
}
You can turn it off and on.... in my code i did something like this as i needed to turn it off when the keyboard was not showing, you can apply it to your situation:
call this is viewdidload etc:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(notifyShowKeyboard:) name:UIKeyboardDidShowNotification object:nil];
[center addObserver:self selector:#selector(notifyHideKeyboard:) name:UIKeyboardWillHideNotification object:nil];
then create the two methods:
-(void) notifyShowKeyboard:(NSNotification *)inNotification
{
tap.enabled=true; // turn the gesture on
}
-(void) notifyHideKeyboard:(NSNotification *)inNotification
{
tap.enabled=false; //turn the gesture off so it wont consume the touch event
}
What this does is disables the tap. I had to turn tap into a instance variable and release it in dealloc though.

UIPanGestureRecognizer on MKMapView?

I would like to add some logic when user moves with map view i. e. he does a pan touch. But when I add the gesture recognizer and I want to log the touch, nothing happens. When I try it in another view controller and add the recognizer to controller's view then it works ok.
Here's my code (map view is a property of application delegate because I need to do some other things with it even if it isn't visible):
- (void)viewDidLoad
{
...
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(showPan)];
[appDelegate.mapView addGestureRecognizer:panGesture];
[panGesture release];
}
- (void)showPan
{
NSLog(#"pan!");
}
I use latest iOS 4.2.1
Thanks for any advice.
Ok, because no one knew, I had to spent one Apple technical support consult for it. ;o)
Because MKMapView evidently has its own recognizers to interact with user, you have to adhere to the UIGestureRecognizerDelegate protocol and implement (BOOL)gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: like this:
- (void)viewDidLoad
{
...
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(showPan)];
panGesture.delegate = self;
[appDelegate.mapView addGestureRecognizer:panGesture];
[panGesture release];
}
- (void)showPan
{
NSLog(#"pan!");
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
Then it works like a charm.
Swift 5
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGesture))
panGesture.delegate = self
self.mapView.addGestureRecognizer(panGesture)
#objc func panGesture (sender: UIPanGestureRecognizer) {
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}