UIScrollView inside UIPageViewController: 1 and 2 finger swipe gestures - ios

Structure:
I have one UIPageViewController, currently containing two UIViewController (called e.g. MenuVC and ImageScrollVC).
In the second UIViewController (the ImageScrollVC) I have a UIScrollView. This UIScrollView contains multiple images. The user should be able to swipe between the images.
I want:
The UIPageViewController should only scroll when the user is using two fingers.
** So the user can swipe between the 'MenuVC' and the ImageScrollVC with two fingers, anytime.
The UIScrollView should only scroll when the user is using one finger.
** With the one finger swipe gesture the user can change the images.
Is this possible?
My assumption is to forward a two finger swipe gesture to the underlying UIPageViewController, but I don't know how to do this.
Update:
My ViewController structure:
RootViewController is implementing the UIPageViewController (exactly like the Apple template).
MenuViewControlleris the first ViewController in the PageViewController and ImageScrollViewController the second one with the UIScrollView for the images.

Your assumption is right on. Normally your UIScrollView in the nested ImageScrollVC will handle both one- and two-finger swipes. You can use a custom subclass of UIScrollView in your ImageScrollVC to ignore two-finger swipes so that they will continue up the chain and be handled by the UIPageViewController. For more details, check out this related question, from which the sample code below is based.
The gist of it is that you'll implement -gestureRecognizerShouldBegin: in your UIScrollView subclass; only return YES if the superclass returns YES, and if the number of touches is 1.
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)recognizer
{
return recognizer.numberOfTouches == 1 && [super gestureRecognizerShouldBegin:recognizer];
}

I think I solved it. It feels that the solution is not the best or nicest one, but it works.
What I did:
I created a new UIPanGestureRecognizer and added it to the imageScrollView. This gestureRecognizer has min. and max. touches to 2. In the action method of this recognizer I enabled and disabled scrolling of the imageScrollView.
Also I created a own subclass of UIScrollView and implemented the gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer to return YES.
In code:
MenuViewController: My first UIViewController in the pageVC. The class is doing nothing (for the purpose of this problem). This is only a placeholder to swipe with two fingers and have something to show.
RootViewController:
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
//... exactly the same code as in the Apple template
//the UIPageViewController should only use 2 finger:
for (UIView* view in [self.pageViewController.view subviews]) {
if ([view isKindOfClass:[UIScrollView class]]) {
UIScrollView* scrollView = (UIScrollView*)view;
scrollView.panGestureRecognizer.minimumNumberOfTouches = 2;
scrollView.panGestureRecognizer.maximumNumberOfTouches = 2;
}
}
ImageScrollViewController:
//self.scrollView is from type 'CustomScrollView'
-(void)viewDidLoad {
[super viewDidLoad];
// autolayout would be nicer, but this was faster and easier for me
self.scrollView.contentSize = CGSizeMake(self.view.frame.size.width * _pageImages.count, self.scrollView.frame.size.height);
//... a for loop to add all UIImageViews as a subView to self.scrollView
UIPanGestureRecognizer *twoFingerPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
twoFingerPan.minimumNumberOfTouches = 2;
twoFingerPan.maximumNumberOfTouches = 2;
[self.scrollView addGestureRecognizer:twoFingerPan];
twoFingerPan.delegate = (CarScrollView*)self.scrollView;
}
//the inner scrollView for the images should not scroll when two fingers are active
- (void)handlePan:(UIPanGestureRecognizer*)recognizer {
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
case UIGestureRecognizerStatePossible:
self.scrollView.scrollEnabled = NO;
break;
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateFailed:
self.scrollView.scrollEnabled = YES;
default:
break;
}
}
CustomScrollView: I created a subclass for the UIScrollView with the images.
#interface CarScrollView : UIScrollView <UIGestureRecognizerDelegate>
#implementation CarScrollView
//Method has to return YES so that a two finger pan gesture will be used for the outer scrollView/pageViewController
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}

Related

Dragging views on a scroll view: touchesBegan is received, but no touchesEnded or touchesCancelled

As an iOS programming newbie I am struggling with a word game for iPhone.
The app structure is: scrollView -> contentView -> imageView -> image 1000 x 1000 (here fullscreen):
I think I have finally understood how to use an UIScrollView with Auto Layout enabled in Xcode 5.1:
I just specify enough constraints (dimensions 1000 x 1000 and also 0 to the parent) for the contentView and this defines the _scrollView.contentSize (I don't have to set it explicitly) - after that my game board scrolls and zooms just fine.
However I have troubles with my draggable letter tiles implemented in Tile.m.
I use touchesBegan, touchesMoved, touchesEnded, touchesCancelled and not gesture recognizers (as often suggested by StackOverflow users), because I display larger letter tile image with shadow (the bigImage) on touchesBegan.
My dragging is implemented in the following way:
In touchesBegan I remove the tile from contentView (and add it to the main app view) and display bigImage with shadow.
In touchesMoved I move the tile
In touchesEnded or touchesCancelled I display smallImage with shadow again and - add the tile to the contentView or leave it in the main view (if the tile is at the bottom of the app).
My problem:
Mostly this works, but sometimes (often) I see that only touchesBegan was called, but the other touchesXXXX methods are never called:
2014-03-22 20:20:20.244 ScrollContent[8075:60b] -[Tile touchesBegan:withEvent:]: Tile J 10 {367.15002, 350.98877} {57.599998, 57.599998}
Instead the scrollView is scrolled by the finger, underneath the big tile.
This results in many big tiles with shadows sitting on the screen of my app, while the scroll view is being dragged underneath them:
How to fix this please?
I know for sure that my structure of the app (with custom UIViews dragged in/out of a UIScrollView) is possible - by looking at popular word games.
I use tile.exclusiveTouch = YES and a custom hitTest method for the contentView - but this doesn't help:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
return result == self ? nil : result;
}
UPDATE 1:
I've tried adding the following code to handleTileTouched:
_contentView.userInteractionEnabled = NO;
_scrollView.userInteractionEnabled = NO;
_scrollView.scrollEnabled = NO;
and then set it back to YES in handleTileReleased of ViewController.m - but this does not help and also looks more like a hack to me.
UPDATE 2:
Having read probably everything related to UIScrollView, hitTest:withEvent: and pointInside:withEvent: - on the web (for ex. Hacking the responder chain and Matt Neuburg's Programming iOS book), StackOverflow and Safari, it seems to me, that a solution would be to implement the hitTest:withEvent: method for the main view of my app:
If a Tile object is hit, it should be returned. Otherwise - the scrollView should be returned.
Unfortunately, this doesn't work - I am probably missing something minor.
And I am sure that a good solution exists - by studying popular word games for iOS. For example dragging and placement of letter tiles works very smooth in Zynga's Words with Friends ® app and in the screenshots below you can see them probably using UIScrollView (the scroll bars are visible in the corner) and displaying a tile shadow (probably in touchesBegan method):
UPDATE 3:
I've created a new project to test gesture recognizer suggested by TomSwift and it shows the problem I have with gesture recognizers: the tile size changes too late - it happens, when the user starts moving the tile and not at the moment he touches it:
The problem here is that removing a view from the view hierarchy confuses the system, the touch is lost. It is the same issue (internally gesture recognizers use the same touchesBegan: API).
https://github.com/LeoNatan/ios-newbie/commit/4cb13ea405d9f959f4d438d08638e1703d6c0c1e
(I created a pull request.)
What I changed was to not remove the tile from the content view when touches begin, but only move on touches end or cancel. But this creates a problem - when dragging to the bottom, the tile is hidden below the view (due to scrollview clipping to its bounds). So I created a cloned tile, add it as a subview of the view controller's view and move that together with the original tile. When touches end, I remove the cloned tile and place the original where it should go.
This is because the bottom bar is not part of the scrollview hierarchy. If it was, the entire tile cloning would not be necessary.
I also streamlined the positioning of tiles quite a bit.
you could set the userInteractionEnabled of the scrollview to NO while you are dragging the tile, and set it back to YES when the tile dragging ended.
You should really try using a gesture recognizer instead of the raw touchesBegan/touchesMoved. I say this because UIScrollView is using gesture recognizers and by default these will cede to any higher-level gesture recognizer that is running.
I put together a sample that has a UIScrollView with an embedded UIImageView. As with your screenshot, below the scrollView I have some UIButton "Tiles", which I subclassed as TSTile objects. The only reason I did this was to expose some NSLayoutConstraints to access/alter their height/width (since you're using auto layout vs. frame manipulation). The user can drag tiles from their starting place into the scroll view.
This seems to work well; I didn't hook up the ability to drag a tile once it is re-parented in the scrollview. But that shouldn't be too hard. For that you might consider placing a long-tap gesture recognizer in each tile, then when it fires you would turn off scrolling in the scrollview, such that the top-level pan gesture recognizer would kick in.
Or, you might be able to subclass the UIScrollView and intercept the UIScrollView's pan-gesture-recognizer delegate callbacks to hinder panning when the user starts from a tile.
#interface TSTile : UIButton
//$hook these up to width/height constraints in your storyboard!
#property (nonatomic, readonly) IBOutlet NSLayoutConstraint* widthConstraint;
#property (nonatomic, readonly) IBOutlet NSLayoutConstraint* heightConstraint;
#end
#implementation TSTile
#synthesize widthConstraint,heightConstraint;
#end
#interface TSViewController () <UIScrollViewDelegate, UIGestureRecognizerDelegate>
#end
#implementation TSViewController
{
IBOutlet UIImageView* _imageView;
TSTile* _dragTile;
}
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer* pgr = [[UIPanGestureRecognizer alloc] initWithTarget: self action: #selector( pan: )];
pgr.delegate = self;
[self.view addGestureRecognizer: pgr];
}
- (UIView*) viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return _imageView;
}
- (BOOL) gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint pt = [gestureRecognizer locationInView: self.view];
UIView* v = [self.view hitTest: pt withEvent: nil];
return [v isKindOfClass: [TSTile class]];
}
- (void) pan: (UIGestureRecognizer*) gestureRecognizer
{
CGPoint pt = [gestureRecognizer locationInView: self.view];
switch ( gestureRecognizer.state )
{
case UIGestureRecognizerStateBegan:
{
NSLog( #"pan start!" );
_dragTile = (TSTile*)[self.view hitTest: pt withEvent: nil];
[UIView transitionWithView: self.view
duration: 0.4
options: UIViewAnimationOptionAllowAnimatedContent
animations:^{
_dragTile.widthConstraint.constant = 70;
_dragTile.heightConstraint.constant = 70;
[self.view layoutIfNeeded];
}
completion: nil];
}
break;
case UIGestureRecognizerStateChanged:
{
NSLog( #"pan!" );
_dragTile.center = pt;
}
break;
case UIGestureRecognizerStateEnded:
{
NSLog( #"pan ended!" );
pt = [gestureRecognizer locationInView: _imageView];
// reparent:
[_dragTile removeFromSuperview];
[_imageView addSubview: _dragTile];
// animate:
[UIView transitionWithView: self.view
duration: 0.25
options: UIViewAnimationOptionAllowAnimatedContent
animations:^{
_dragTile.widthConstraint.constant = 40;
_dragTile.heightConstraint.constant = 40;
_dragTile.center = pt;
[self.view layoutIfNeeded];
}
completion:^(BOOL finished) {
_dragTile = nil;
}];
}
break;
default:
NSLog( #"pan other!" );
break;
}
}
#end
I also think you should use a UIGestureRecognizer, and more precisely a UILongPressGestureRecognizer on each tile that once recognized will handle pan.
For fine grained control you can still use the recognizers' delegate.

Draggable UIView stops posting touchesBegan after being added to UIScrollView

In Xcode 5.1 I have created a simple test app for iPhone:
The structure is: scrollView -> contentView -> imageView -> image 1000 x 1000 on the top.
And on the bottom of the single view app I have seven draggable custom UIViews.
The dragging is implemented in Tile.m with touchesXXXX methods.
My problem is: once I add a draggable tile to the contentView in my ViewController.m file - I can not drag it anymore:
- (void) handleTileMoved:(NSNotification*)notification {
Tile* tile = (Tile*)notification.object;
//return;
if (tile.superview != _scrollView && CGRectIntersectsRect(tile.frame, _scrollView.frame)) {
[tile removeFromSuperview];
[_contentView addSubview:tile];
[_contentView bringSubviewToFront:tile];
}
}
The touchesBegan isn't called for the Tile anymore as if the scrollView would mask that event.
I've searched around and there was a suggestion to extend the UIScrollView class with the following method (in my custom GameBoard.m):
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
NSLog(#"%s: %hhd", __PRETTY_FUNCTION__,
[result.superview isKindOfClass:[Tile class]]);
self.scrollEnabled = ![result.superview isKindOfClass:[Tile class]];
return result;
}
Unfortunately this doesn't help and prints 0 in debugger.
The problem is, partly, because user interactions are disabled on the content view. However, enabling user interactions disables scrolling as the view captures all touches. So here is the solution. Enable user interactions in storyboard, but subclass the content view like so:
#interface LNContentView : UIView
#end
#implementation LNContentView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
return result == self ? nil : result;
}
#end
This way, hit test passes only if the accepting view is not self, the content view.
Here is my commit:
https://github.com/LeoNatan/ios-newbie
The reason Tile views don't get touches is that scroll view's pan gesture recogniser consumes the events. What you need is, attach a UIPanGestureRecongnizer to each of your tiles and configure them as follows:
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pan:)]; // handle drag in pan:method
[tile addGestureRecognizer:pan];
UIPanGestureRecognizer *scrollPan = self.scrollView.panGestureRecognizer;
[scrollPan requireGestureRecognizerToFail:pan];
Here you let scroll view's pan gesture recogniser know that you only wish scrolling to happen if none of the tiles are bing dragged.
I've checked the approach — it does work indeed. Regarding your code, you'll need to handle all touches in the gesture recogniser rather than Tile view because touch events may be consumed/delayed by hit-tested view's gesture recogniser before they reach the view itself. Please refer to UIGestureRecognizer documentation to learn more about the topic.
It looks as ir one of the views in the hierarchy is capturing the events.
Have a look at the section
The Responder Chain Follows a Specific Delivery Path
Of the Apple doc's here
Edit:
Sorry I was writing from memory. This is how i resolved a similar issue in an app of myself:
I use UITapGestureRecognizer in the view(s) that I want to detect the touch. Implement the following delegate method of the UITapGestureRecognizer:
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
The touches' set contains all the objects (views) that received the event.

Hide UITableView on touches outside the tableview

I have a small UITableView that is hidden when the view is loaded. When i click on "SHOW" UIButton, the UITableView is made visible by myTableView.hidden=NO;
I want to hide UITableView when a user touches outside its frame. Thanks for any help!
Best Approach
Simple.Before show up the UITable View add one more grayed out/Transparent view then add tap gesture recogniser on it to hide it . That's it.
Show Overlay View first - alpha will be 0.5f and background color should be clear color.
show the table view.
NOTE: over lay view should have tap recogniser which will hide the overlay and table view
in View did load
UITapGestureRecognizer *tapRecog = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(overlayDidTap:)];
[myOverLayView addGestureRecognizer:tapRecog];
- (void)overlayDidTap:(UITapGestureRecognizer *)gesture
{
//hide both overlay and table view here
}
Bad Approach
We should not add tap recogniser on main view itself. Because it may have lots of
controls inside of it. so when user tap on it. it will perform its operation. So to avoid
it we can simulate the same behaviour by above approach
You can get touch position by this:
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTapGestureCaptured:)];
[self.view addGestureRecognizer:singleTap];
- (void)singleTapGestureCaptured:(UITapGestureRecognizer *)gesture
{
CGPoint touchPoint=[gesture locationInView:self.View];
}
Then just check if the point is in tableview frame. If not then hide the tableview. Hope this help. :)
Subclass UITableView, override pointInside:withEvent:. It is templated for this reason.
Something like:
-(BOOL)pointInside:(CGPoint) point withEvent:(UIEvent*) event
{
BOOL pointIsInsideTableView = [super pointInside:point withEvent:event];
BOOL pointIsOutsideTableView = // Some test
return pointIsInsideTableView || pointIsOutsideTableView;
}
So you can catch the touch in table view implementation, where it belongs logically in this case.

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