I have a UIScrollView with the same frame as the containing view controller.
When the zoom scale of the scrollview is greater than 1, swiping left to right starting from the left 50% of the screen causes the interactivePopGestureRecognizer to activate and dismiss the view controller.
This only happens when the device is in landscape. It works fine when in portrait.
Is there any special handling needed to make this gesture work normally with a scrollview?
1、set interactivePopGestureRecognizer delegate to self (Custom UINavigationController)
self.interactivePopGestureRecognizer.delegate = self;
2、enable simultaneously recognize UIScrollView UIGestureRecognizer
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (self.interactivePopGestureRecognizer == gestureRecognizer) {
if ([otherGestureRecognizer.view isKindOfClass:UIScrollView.class]) {
UIScrollView *scrollView = (UIScrollView *)otherGestureRecognizer.view;
if ((scrollView.contentSize.width > CGRectGetWidth(self.view.bounds) && scrollView.contentOffset.x == 0)) {
return YES;
}
}
}
return NO;
}
The 1 step:
#implementation UIScrollView (AllowPanGestureEventPass)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]
&& [otherGestureRecognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]])
{
return YES;
}
else
{
return NO;
}
}
The 2 step:
[_scrollView.panGestureRecognizer requireGestureRecognizerToFail:screenEdgePanGestureRecognizer];
Related
I'm having a UITextView, in which I can't see the long press text magnifier. Is there a way to force that magnifier to appear?
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
BOOL isAtLeastOneGestureInlineTextView = [self isInlineTextView:gestureRecognizer.view] || [self isInlineTextView:otherGestureRecognizer.view];
if ((gestureRecognizer == [self longPressGesture]) && !isAtLeastOneGestureInlineTextView) {
return NO;
}
if (gestureRecognizer == [self panGesture]) {
return NO;
}
return YES;
}
- (BOOL)isInlineTextView:(UIView *)view {
return [view isKindOfClass:[SFGInlineTextView class]];
}
Apparently there was this check which disabled long press. To preserve former implementation, I've checked the gestures to not be the desired textView.
Please help me with the below scenario.
Self.view (Swipe Gesture added here)
UicollectionView Object in subView (Default pan,swipe gestures are there)
Want to disable scrollview scrolling/failCollectionViewPan gesture for 2 finger swipe event
Means collection view will not scroll if 2 finger swipe executed
Another approach can be to disable Collection view scrolling while 2 fingers used. Over here, I want not to swipe collection view on 2 finger swipe.
I have implemented this code, but its slowing down scrolling.
[self.collectionView.panGestureRecognizer shouldBeRequiredToFailByGestureRecognizer:_swipeL];
Then I have implemented below codes
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
return YES;
}
Now both, Swipe and Scroll are working together.
Then, I have tried in a below manner, but still not got fixed.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if([gestureRecognizer isKindOfClass:[UISwipeGestureRecognizer class]]){
if(gestureRecognizer.numberOfTouches==2){
if( [[otherGestureRecognizer.view class] isSubclassOfClass:[UITableViewCell class]] ||
[NSStringFromClass([otherGestureRecognizer.view class]) isEqualToString:#"UITableViewCellScrollView"] ||
[NSStringFromClass([otherGestureRecognizer.view class]) isEqualToString:#"UITableViewWrapperView"] || [NSStringFromClass([otherGestureRecognizer.view class]) isEqualToString:#"UIScrollViewPanGestureRecognizer"] || [NSStringFromClass([otherGestureRecognizer.view class]) isEqualToString:#"UIScrollViewPagingSwipeGestureRecognizer"])
{
NSLog(#"Allow&Disable %#", [otherGestureRecognizer description]);
[gestureRecognizer requireGestureRecognizerToFail:otherGestureRecognizer];
return NO;
}
}
}
return YES;
}
Also I have implemented below 2 methods to fix it but fails.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
Its is able to fix for ScrollView with such ways, but the same ways are not working for UICollectionView. Because of, colection view's default pan gesture, it cannot be able to modified. While tring this way, app crashed.
If you want to detect pan gesture, try something like this. But you need to recognise direction of pan gesture:
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGesture:)];
panGesture.delegate = self;
[self.yourCollectionView addGestureRecognizer:panGesture];
}
- (void)panGesture:(UIPanGestureRecognizer *)gesture;
{
if (gesture.numberOfTouches == 2) {
NSLog(#"Two fingers pan gesture");
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]
&& gestureRecognizer.numberOfTouches == 2) {
return NO;
}
return YES;
}
Your problem with UISwipeGestureRecognizer is, that it fires later than UIPanGesture, so in the shouldRecognizeSimultaneouslyWithGestureRecognizer delegate call, swipe gesture is always otherGestureRecognizer, and UIPanGesture is always gestureRecognizer, and in this function you can only disable otherGestureRecoginzer...
UPDATE:
Another solution: use another UIPanGestureRecognizer to disable scroll pan gesture:
- (void)viewDidLoad
{
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeGesture:)];
swipeGesture.direction = UISwipeGestureRecognizerDirectionDown;
swipeGesture.delegate = self;
swipeGesture.numberOfTouchesRequired = 2;
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:nil];
panGesture.delegate = self;
[self.tableView addGestureRecognizer:panGesture];
[self.tableView addGestureRecognizer:swipeGesture];
}
- (void)swipeGesture:(UIPanGestureRecognizer *)gesture;
{
NSLog(#"Two fingers swipe gesture");
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]
&& gestureRecognizer.numberOfTouches == 2) {
return NO;
}
return YES;
}
This answer might be late, but I did have a similar problem today and couldn't find a solution, until I figured something out myself. It's quite simple, actually.
Swift 5
We create a gesture
let gesture = UIPanGestureRecognizer(target: self, action: #selector(functionCall))
gesture.minimumNumberOfTouches = 2
gesture.maximumNumberOfTouches = 2
We then assign that gesture to the collectionView, effectively overwriting the scroll gesture on it.
collectionView.addGestureRecognizer(gesture) // simply overwrites the 2 finger gesture on the collectionView
It now is possible to scroll the collectionView with 1, 3 or more fingers, but the 2 finger pan gesture is blocked.
We currently are using a solution similar to the one mentioned here (see Ares's answer). This does not seem to work in iOS8.
I have a form sheet, and I want to dismiss it as soon as the user taps on the dimmed view 'behind' the form sheet.
Previously, this seemed possible by adding a gesture recogniser to the window, and checking tap-location to see if it was outside the current form sheet;
I also noticed the point needs to be converted (switch x and y) of the device is used in landscape mode. Other than that, right now it only receives gestures that occurred from inside the form sheet, where before any tap gesture anywhere on the screen would trigger an event.
- (void)viewDidLoad
{
[super viewDidLoad];
self.recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
self.recognizer.numberOfTapsRequired = 1;
self.recognizer.cancelsTouchesInView = NO;
[self.view.window addGestureRecognizer:self.recognizer];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil];
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation) && IOS8)
{
location = CGPointMake(location.y, location.x);
}
// if tap outside pincode inputscreen
BOOL inView = [self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil];
if (!inView)
{
[self.view.window removeGestureRecognizer:sender];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
As mentioned in the thread you referenced, you should add a UIGestureRecognizerDelegate and implement the methods:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return YES;
}
I have a gesture recognizer on my view controller which I use to slide in/out the navigation bar. Unfortunately there is a bad NSZombie that makes the whole app crash when I go swipe to go to the previous controller. It's very difficult to track because it only happens in a specific view controller.
Here is the error:
[MyViewController gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:]: message sent to deallocated instance
And here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UIPanGestureRecognizer *pgr = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pgr:)];
pgr.delegate = self;
[self.view addGestureRecognizer:pgr];
}
- (void)pgr:(UIPanGestureRecognizer *)gesture {
// Check if this is the first touch
if (gesture.state == UIGestureRecognizerStateBegan) {
CGPoint point = [gesture locationInView:gesture.view];
self.start = point.y;
}
CGPoint point = [gesture locationInView:gesture.view];
self.currentX = point.x;
self.offsetY = point.y - self.start;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if ([gestureRecognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) {
return YES;
}
return NO;
}
The delegate method is
- (BOOL)shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
Try to use
- (BOOL)shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([otherGestureRecognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) {
return YES;
}
return NO;
}
I'm trying to add a pan gesture recognizer to a view containing a scrollview, but I guess I've problems with priorities.
My global UIView has a UIPanGestureRecognizer set like this:
_bottomPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(bottomPanGestureDetected:)];
_bottomPanGestureRecognizer.minimumNumberOfTouches = 2;
_bottomPanGestureRecognizer.maximumNumberOfTouches = 2;
_bottomPanGestureRecognizer.delaysTouchesBegan = NO;
_bottomPanGestureRecognizer.delaysTouchesEnded = NO;
I want to recognize this gesture to display another view from the bottom with some sort of pinch down-to-up.
The problem is that the scrollview is recognizing its own pan gesture before mine.
So I tried to delay it thanks to:
[_scrollView.panGestureRecognizer requireGestureRecognizerToFail:_bottomPanGestureRecognizer];
And it's working, the scrollview event is fired after my two finger down to up recognizer, but the problem is now when I only use one finger to scroll in the scrollview, the scroll works after a small delay.
I would like to have no delay for this event, is this possible? Any idea welcomed!
Cheers.
Cyril
In case it isn't solved yet, i solved the problem for me.
I added a UIPanGestureRecognizer to a UIScrollView to detect two finger pan gestures and the default UIScrollView behaviour (scrolling to something) still workes.
So what i did is to add the UIPanGestureReconizer to the UIScrollView:
UIPanGestureRecognizer *pangestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(displayReloadIndicator:)];
pangestureRecognizer.minimumNumberOfTouches = 2;
pangestureRecognizer.delegate = self;
[self.scrollView addGestureRecognizer:pangestureRecognizer];
[pangestureRecognizer release];
After this i added the code:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
After this, i implemented the pan gesture recognizers action method.
- (void) displayReloadIndicator:(UIPanGestureRecognizer*) panGestureRecognizer {
UIGestureRecognizerState gestureRecognizerState = gestureRecognizer.state;
CGPoint translation = [gestureRecognizer translationInView:self.scv_bibgesamt];
if (gestureRecognizerState == UIGestureRecognizerStateBegan) {
// create a UIView with all the Pull Refresh Headers and add to UIScrollView
// This is really much lines of code, but its simply creating a UIView (later you'll find a myRefreshHeaderView, which is my base view) and add UIElements e.g. UIActivityIndicatorView, a UILabel and a UIImageView on it
// In iOS 6 you will also have the possibility to add a UIRefreshControl to your UIScrollView
}
else if (gestureRecognizerState == UIGestureRecognizerStateEnded
|| gestureRecognizerState == UIGestureRecognizerStateCancelled) {
if (translation.y >= _myRefreshHeaderView.frame.size.height + 12) { // _myRefreshHeaderView is my baseview
//so the UIScrollView has been dragged down with two fingers over a specific point and have been release now, so we can refresh the content on the UIScrollView
[self refreshContent];
//animatly display the refresh view as the top content of the UIScrollView
[self.scrollView setContentOffset:CGPointMake(0, myRefreshHeaderView.frame.size.height) animated:YES];
}
else {
//the UIScrollView has not been dragged over a specific point so don't do anything (just scroll back to origin)
[self.scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
//remove the view (because it's no longer needed)
[_myRefreshHeaderView removeFromSuperview];
}
}
UPDATE:
In case you may wish to integrate the swipe back functionality from your navigationcontroller, you should integrate following code:
- (void) viewDidLoad {
[super viewDidLoad];
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
//setup view controller
}
and
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (gestureRecognizer == _panGestureRecognizer
&& [self.navigationController.viewControllers count] > 1) {
CGPoint point = [touch locationInView:self.view.window];
if (point.x < 20
|| point.x > self.view.window.frame.size.width - 20) {
return NO;
}
}
return YES;
}
Implement panRecognizer delegate to enable simultaneously recognize UIScrollView UIGestureRecognizer
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (_panRecognizer == gestureRecognizer) {
if ([otherGestureRecognizer.view isKindOfClass:UIScrollView.class]) {
UIScrollView *scrollView = (UIScrollView *)otherGestureRecognizer.view;
if (scrollView.contentOffset.x == 0) {
return YES;
}
}
}
return NO;
}
SWIFT 4
If using a scrollView you can use below, it is only a panGestureRecognizer when a scrollView is dragging at the very top:
translation.y > 0 means you are moving from top to bottom and
locationInScrollView.y < 500.0 means you end dragging at 500 or less (you can customize this) to prevent that the refresh is done in middle or bottom of the scroll.
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview)
let locationInScrollView = scrollView.panGestureRecognizer.location(in: scrollView)
if translation.y > 0 && locationInScrollView.y < 500.0 {
print("scrollView refresh: Y: \(locationInScrollView.y)")
setupUI()
}
}