Disable horizontal scrolling on UITableViewCell when UITableView is still scrolling vertically - ios

I added a UIPanGestureRecognizer on my UITableViewCell subclass so that when the user swipes to the left, the buttons underneath are revealed (kind of like in the Mail app). I do this in awakeFromNib:
// Add a pan gesture recognizer to the pannable view.
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panScrollView:)];
panGesture.delegate = self;
[self.pannableView addGestureRecognizer:panGesture];
I want to allow the table view to scroll despite my custom gesture recognizer so I also have the following in the same UITableViewCell subclass:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
My problem now is I don't know how to allow only one gesture recognizer at a time, so that when the user is side-swiping, the table view doesn't scroll, and vice versa. Help?
What I've tried that doesn't work
Method 1
Implement UIGestureRecognizerDelegate in the table view's view controller and require the failure of any other gesture recognizer besides the vertical one which is for scrolling the table view.
Set the table view's panGestureRecognizer's delegate to the view controller that contains it (say it's ContainingViewController).
self.tableView.panGestureRecognizer.delegate = self;
Make ContainingViewController implement UIGestureRecognizerDelegate.
Implement shouldRequireFailureOfGestureRecognizer: and return YES (I'm assuming that the otherGestureRecognizer will be the horizontal panning gesture).
Error: 'UIScrollView's built-in pan gesture recognizer must have its scroll view as its delegate.'

Create a bool that allows your panGesture to action inside panScrollView
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
self.blockPanGesture = YES;
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
self.blockPanGesture = NO;
}
Then
-(void)panScrollView:(UIPanGestureRecognizer *)panGestureRecognizer
{
if(self.blockPanGesture == NO)
{
// do the stuff
}
}
If you are panning the entire tableview personally I'd put the gesture recogniser on the tableView itself... Otherwise there's also
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return YES if you want the specified item to be editable.
return YES;
}
which handles all this for you... but I'm assuming there's a reason you don't want to use this. You can also build on this logic, for example you might want to tweak when the scrollView can drag as well, by putting similar checks in -(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView etc.

Using Magoo solution, I did something simillar which works for me. Hope it helps someone.
In Controller class
#pragma mark - UIScrollViewDelegate methods
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
ApplicationDelegate.blockPanGesture = YES;
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
ApplicationDelegate.blockPanGesture = NO;
}
Inside Cell:
- (IBAction)panGestureRecognizer:(UIPanGestureRecognizer *)panGesture
{
if(ApplicationDelegate.blockPanGesture)
return;
// do your stuff
}
Just FYI: have dragged UIPanGestureRecognizer in Cell's xib file and created above IBOutletAction |panGestureRecognizer:|

Have you tried setting the bounces property to NO?

Related

ScrollView collide with UItableview

My UIScrollView inside a nested UITableView, in which UITableView has a left row pop-up menu of gestures, and now the two views of the gesture conflict. Now the question is: how to solve this problem without changing the UITableView ?
PS: I have been set UIScrollView ScrollEnabled=false
Your question is different but as you agree that your main problem is conflict between the gestures, that means you are not able to differentiate between gesture of two views,
To solved there are two ways, you need to receive gesture based on condition in gestureRecognizer delegate method, you can either check the class which received the gesture or by checking class or by tag
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch
{
if([touch.view class] == [UITableView class]){
return NO;
} else if (touch.view.tag == 100) {
return NO;
} else if ([NSStringFromClass([touch.view class]) isEqualToString:#"UITableViewCellContentView"]) {
return NO;
}
return YES;
}
What this delegate would do is, it will call the gesture handler method only for the view which you want to handle gesture and also you can differentiate between the gesture recogniser.

Adding a UIGestureRecognizer taking priority over all other interactions

When I tap on a UIButton, a UIView MyView appear from the bottom a cover a third of the screen. I would like that when I tap somewhere outside this view, it disappears.
I thought about adding another transparent UIView right under MyView and add a tab gesture on it with the dismiss function but I'm sure there is something cleaner than this.
So I thought about adding the tap gesture MyTapGesture to dismiss MyView on self.view of the UIViewController. The problem is that outside this view, I have other UIControls and gestures that capture also any touch at the same time than MyTapGesture.
How can I make MyTapGesture the priority gesture outside MyView and ignore all other gesture, taps, etc...?
You may have to use the gesture delegate methods to handle two tapGestureRecognizer activate the one you need depending on scenario
#pragma mark - UIGestureRecognizerDelegate methods
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if ([tapGestureRecognizer1 isEqual:gestureRecognizer]) {
return [tapGestureRecognizer2 isEqual:otherGestureRecognizer];
}
if ([tapGestureRecognizer2 isEqual:gestureRecognizer]) {
return [tapGestureRecognizer1 isEqual:otherGestureRecognizer];
}
return NO;
}

Tap on UIBarButtonItem is not ignored by TapGestureRecognizer

I have a view with a UIToolbar with a few UIBarButtonItems and a UITableView containing some UITextFields.
I would like to dismiss the keyboard for a textfield with a tap anywhere. Therefore I added a TapGestureRecognizer to the view. To avoid that the TapgestureRecognizer handles taps on the UIBarButtonItems I added the following method (delegate is set).
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
UIView *view = touch.view;
while (view) {
NSLog(#"Class of view: %#", NSStringFromClass([view class]));
view = view.superview;
}
// Disallow recognition of tap gestures in the toolbar
if ([touch.view isKindOfClass:[UIToolbar class]]) {
return NO;
}
if ([touch.view.superview isMemberOfClass:[UIToolbar class]]) {
return NO;
}
return YES;
}
A UIBarButtonItem is not a view itself, but it has UIToolbar as its superview. When I use the above method, the check for isKindOfClass:[UIToolbar class] does not seem to work for all taps on the toolbar. However the check for the superview with isMemberOfClass:[UIToolbar class] works.
I don't understand this. Maybe someone can explain this behavior?
You shouldn't rely on the view hierarchy around private view classes. It could change at any time.
A better approach is to add the gesture to the table view (or other appropriate view which represents the area you're interested in). Just be sure to enable and disable the gesture at appropriate times so as not to block the usual table operation.

UIScrollView doesn't scroll when touching GADBannerView subview

I have a project that uses the Google AdMob Ads SDK. I'm trying to show a few ads on the homepage along with some other buttons, some of which are below the screen.
I've used a UIScrollView and added a few GADBannerViews from DFP inside as well the buttons. The ads load just fine and I can click on the ads and buttons with no problem.
The problem is when I try to scroll the scroll view. If I start touching on the ad view, the scroll view will not scroll. If I start touching anywhere else, like a button or a blank space, the scroll view scrolls properly. It seems that the ad is somehow taking control of the touch events.
I've tried all sorts of fixes such as creating a transparent UIView above the ads, which didn't work because the taps would not register.
I've tried looping through the subviews of the GADBannerView but all the subviews' classes seem proprietary to AdMob or inaccessible. (GADWebView, _UIWebViewScrollView)
I even tried adding the ad to a UITableView to see if it would scroll there, but it did not work either.
My view controller class is quite large so if you need me to post some code, I can create a sample app to demonstrate the problem. A workaround for now is to create UIWebViews with the HTML ad code inside instead of using the GADBannerView. I've tested this and it works, but I really don't want to lose the functionality of the native method.
Is there any way to scroll a UIScrollView if you start touching on the GADBannerView and allow the ad to remain clickable?
Thanks!
This issue can be resolved by subclassing UIScrollView, conforming to the the UIGestureRecognizerDelegate protocol, and returning YES from
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
You shouldn't need to set the delegate; UIScrollView is already set as the delegate of it's gesture recognizers by default.
The problem is a conflict between the gesture recognizer in the UIWebView used by GADBannerView and a custom recognizer in GADBAnnerView.
Without subclassing UIScrollView and changing the gesture recognizer delegate, you can remove this gesture recognizer and set your object as delegate for custom recognizer with this:
- (void)preventBannerCaptureTouch:(GADBannerView*)bannerView
{
for (UIWebView *webView in bannerView.subviews) {
if ([webView isKindOfClass:[UIWebView class]]) {
for (UIGestureRecognizer *gestureRecognizer in webView.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:NSClassFromString(#"GADImpressionTicketGestureRecognizer")]) {
gestureRecognizer.delegate = self;
}
}
for (id view in [[[webView subviews] firstObject] subviews]) {
if ([view isKindOfClass:NSClassFromString(#"UIWebBrowserView")]) {
for (UIGestureRecognizer *recognizer in [view gestureRecognizers]) {
if ([recognizer isKindOfClass:NSClassFromString(#"UIWebTouchEventsGestureRecognizer")]) {
[view removeGestureRecognizer:recognizer];
}
}
return;
}
}
}
}
}
Then you should implement the simultaneous gesture recognizer delegate to allow simultaneously recognise:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
For DFP you can use a DFPSwipeableBannerView instead of a DFPBannerView. Not sure how the orignal GADBanner works tho, but this should be the same. Works in UITableView.
I had to combine two of the answers above:
for (UIWebView *webView in bannerView_.subviews) {
if ([webView isKindOfClass:[UIWebView class]]) {
adView = webView;
}
for (id view in [[[webView subviews] firstObject] subviews]) {
if ([view isKindOfClass:NSClassFromString(#"UIWebBrowserView")]) {
for (UIGestureRecognizer *recognizer in [view gestureRecognizers]) {
if ([recognizer isKindOfClass:NSClassFromString(#"UIWebTouchEventsGestureRecognizer")]) {
[view removeGestureRecognizer:recognizer];
}
}
}
}
webView.scrollView.scrollEnabled = NO;
webView.scrollView.bounces = NO;
}
Where bannerView_ is a GADBannerView
I ran into this issue when trying to add a DFPBannerView as a subview of the contentView of a custom cell in a table view.
For some reason, connecting an IBOutlet defined in my custom cell class to a view in the cell in my storyboard caused the scrolling to start working. The view outlet wasn't even being used, and was completely separate from the banner view - even removing it from its superview allowed the scrolling behaviour to work. Just having an outlet defined and connected to something did the trick.
I wish I could explain why this works, but it remains an iOS mystery.
Unfortunately there is not a not a way to override the scrolling gesture but retain the touch gesture for the ad. The GADBannerView itself needs to control all of the gestures on itself. There is also no way to programmatically send a click to the GADBannerView either, so you can't override the touch behavior either.
I would recommend using ads that are much smaller than your UIScrollView, so you don't have to worry too much about scrolling over an ad.
I solved this by digging down into the GADBannerView and setting the delegate for its web browser view gestures to my own view and then just returning YES for all simultaneous gesture handling:
id webBrowserView = [[[[[[adView subviews] firstObject] subviews] firstObject] subviews] firstObject];
for (UIGestureRecognizer *gestureRecognizer in [webBrowserView gestureRecognizers])
{
[gestureRecognizer setDelegate:self];
}
Then just return yes in the following delegate method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
I ran into the same issue, but decided on a simpler solution. I found that just disabling the ad's web view bounce back allowed the parent scrollview to scroll properly when the ad was touched. Since the ad is the same size as the webview, the only thing the gestureRecognizer was doing was showing the bounce back behavior. Just had to turn that off and left the current gestureRecognizer in place.
- (void)disableBannerBounce:(GADBannerView*)bannerView{
for (UIWebView *webView in bannerView.subviews) {
if ([webView isKindOfClass:[UIWebView class]]) {
webView.scrollView.bounces = NO;
}
}
}

Detecting swipe gestures on UITableViewCell inside UIScrollView

I am hoping someone will be able to help me with a problem that is doing my head in at the moment!
Given the following view hierarchy
I want to be able to detect swipe gestures on my custom UITableViewCell.
I have subclassed the UIScrollView and have a hitTest:withEvent: method that checks whether I am touching the tableview cell (or its content) or not, in which case I set the following scroll view properties:
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
if ([result.superview isKindOfClass:[UITableViewCell class]] || [result.superview tag] == SUBVIEW_TAG)
{
self.canCancelContentTouches = NO;
self.delaysContentTouches = YES;
} else {
self.canCancelContentTouches = YES;
self.delaysContentTouches = NO;
}
return result;
}
I have also implemented:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
if (view.tag == SUBVIEW_TAG || [[view superview] isKindOfClass:[UITableViewCell class]])
return NO;
return YES;
}
And am returning NO in case the view being touched is the table view cell.
These methods are all getting called and performing their actions as expected, but I am still unable to stop the UIScrollView from "hogging" the swipe gesture.
The interesting thing is that if I include the UIView that contains the tableview and cell on both of the methods above (the one with SUBVIEW_TAG) it works perfectly so I am guessing it must be something to do with the fact that UITableView inherits from UIScrollView.
My main goal is to be able to swipe on the cell to reveal more options for the cell. A horizontal swipe anywhere else on that view would be captured by the scroll view and shift the content horizontally as per its normal behaviour.
Any ideas would be very much appreciated!
Thanks!
Rog
I had a similar problem with a swipe detect for a component inside a scrollview and I was able to resolve it with
[scrollView.panGestureRecognizer requireGestureRecognizerToFail:swipeGesture]
Where scrollView is the scroll view object that acts like container and swipeGesture is the component swipe gesture object inside scrollview.
So, you can define a swipe for the cell object like this (for right swipe in the example, custom it as you want)
UISwipeGestureRecognizer* rightSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(yourMethod)];
[rightSwipeRecognizer setDirection:UISwipeGestureRecognizerDirectionLeft];
[cell addGestureRecognizer:rightSwipeRecognizer];
and then do
[scrollView.panGestureRecognizer requireGestureRecognizerToFail:rightSwipeRecognizer]
The documentation of requireGestureRecognizerToFail says:
This method creates a relationship with another gesture recognizer
that delays the receiver’s transition out of
UIGestureRecognizerStatePossible. The state that the receiver
transitions to depends on what happens with otherGestureRecognizer:
If otherGestureRecognizer transitions to
UIGestureRecognizerStateFailed, the receiver transitions to its normal
next state.
if otherGestureRecognizer transitions to
UIGestureRecognizerStateRecognized or UIGestureRecognizerStateBegan,
the receiver transitions to UIGestureRecognizerStateFailed.
An example where this method might be called is when you want a
single-tap gesture require that a double-tap gesture fail.
Availability Available in iOS 3.2 and later.
Hope helps!
The solution is pretty simple. All you need to do is add UIScrollView inside you UITableViewCell. It will prevent "hogging" effect during swipe gesture.

Resources