Question

I'm having problems with a view I'm implementing.

It's a view that shows a pdf page in a CATiledLayer. That tiled view is inside an UISCrollView.

I had the view controlling itself as the "ZoomingPDFView" apple example. I made a few modifications so it would recognize swiping gestures when scrolling is not enabled and advice in various threads and questions on this site. At that time the gestures were called once. But as I needed to decouple the view and delegate the swiping to cache the pages and do a versatile view, I created a view controller to handle the swiping gestures and the page loading methods increase the performance of the pdf view.

Now that I have the view on one side and the controller on the other side, swiping gestures are detected twice and I can't get even a clue of the issue.

this is the console output

2010-11-19 11:45:08.370 ZoomingPDFViewerForIPad[20327:207] initWithFrame and page
2010-11-19 11:45:08.530 ZoomingPDFViewerForIPad[20327:207] drawPage
2010-11-19 11:45:08.531 ZoomingPDFViewerForIPad[20327:207] scale: 1.000000
2010-11-19 11:45:08.531 ZoomingPDFViewerForIPad[20327:207] pdf scale: 1.290062
2010-11-19 11:45:08.532 ZoomingPDFViewerForIPad[20327:207] pdf initial scale: 1.290062
2010-11-19 11:45:15.488 ZoomingPDFViewerForIPad[20327:207] left
2010-11-19 11:45:15.489 ZoomingPDFViewerForIPad[20327:207] left
2010-11-19 11:45:15.490 ZoomingPDFViewerForIPad[20327:207] initWithFrame and page
2010-11-19 11:45:15.538 ZoomingPDFViewerForIPad[20327:207] drawPage
2010-11-19 11:45:15.538 ZoomingPDFViewerForIPad[20327:207] scale: 1.000000
2010-11-19 11:45:15.539 ZoomingPDFViewerForIPad[20327:207] pdf scale: 1.290062
2010-11-19 11:45:15.539 ZoomingPDFViewerForIPad[20327:207] pdf initial scale: 1.290062
2010-11-19 11:45:15.540 ZoomingPDFViewerForIPad[20327:1a07] initWithFrame and page
2010-11-19 11:45:15.541 ZoomingPDFViewerForIPad[20327:5f07] initWithFrame and page
2010-11-19 11:45:15.593 ZoomingPDFViewerForIPad[20327:1a07] drawPage
2010-11-19 11:45:15.594 ZoomingPDFViewerForIPad[20327:1a07] scale: 1.000000
2010-11-19 11:45:15.594 ZoomingPDFViewerForIPad[20327:1a07] pdf scale: 1.290062
2010-11-19 11:45:15.595 ZoomingPDFViewerForIPad[20327:1a07] pdf initial scale: 1.290062
2010-11-19 11:45:15.695 ZoomingPDFViewerForIPad[20327:5f07] drawPage
2010-11-19 11:45:15.704 ZoomingPDFViewerForIPad[20327:5f07] scale: 1.000000
2010-11-19 11:45:15.707 ZoomingPDFViewerForIPad[20327:5f07] pdf scale: 1.290062
2010-11-19 11:45:15.713 ZoomingPDFViewerForIPad[20327:5f07] pdf initial scale: 1.290062

here's the code:

#import <UIKit/UIKit.h>

@class TiledPDFView;
@protocol PDFScrollViewDelegate;

@interface PDFScrollView : UIScrollView <UIScrollViewDelegate> {
 // The TiledPDFView that is currently front most
 TiledPDFView *pdfView;
 // The old TiledPDFView that we draw on top of when the zooming stops
 TiledPDFView *oldPDFView;


 // A low res image of the PDF page that is displayed until the TiledPDFView
 // renders its content.
 UIImageView *backgroundImageView;


 id<PDFScrollViewDelegate,NSObject> pdfViewDelegate;

 // current pdf zoom scale
 CGFloat pdfScale;

 CGPDFPageRef page;
 CGPDFDocumentRef pdf;
 CGFloat initialScale;
 TiledPDFView *initialTiledView;
 int currentPage;
 int pageCount;

 UITapGestureRecognizer *doubleTap,*twoFingerDoubleTap;
 UISwipeGestureRecognizer *rightSwipe, *leftSwipe;



}

@property (nonatomic,retain) id<PDFScrollViewDelegate> pdfViewDelegate;
-(id)initWithFrame:(CGRect)rect;
-(id)initWithFrame:(CGRect)frame andPDFPage:(CGPDFPageRef)aPage;
-(void)enableGestures;
-(void)drawPage;
@end

@implementation PDFScrollView
@synthesize pdfViewDelegate;

….

-(void)enableGestures{
 leftSwipe = [[UISwipeGestureRecognizer alloc ]initWithTarget:self action:@selector(handleRightSwipe:)];

 leftSwipe.direction = UISwipeGestureRecognizerDirectionRight;

 [self addGestureRecognizer:leftSwipe];

 //add right swipe
 rightSwipe = [[UISwipeGestureRecognizer alloc ]initWithTarget:self action:@selector(handleLeftSwipe:)];
 rightSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
 [self addGestureRecognizer:rightSwipe];



 doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleDoubleTap:)];
 doubleTap.numberOfTapsRequired =2;
 doubleTap.numberOfTouchesRequired =1;
 [self addGestureRecognizer:doubleTap];


 twoFingerDoubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleTwoFingerDoubleTap:)];
 twoFingerDoubleTap.numberOfTapsRequired =2;
 twoFingerDoubleTap.numberOfTouchesRequired =2;
 [self addGestureRecognizer:twoFingerDoubleTap];


}

// some more code
@end

#import <UIKit/UIKit.h>
#import "PDFScrollViewDelegate.h"
@class TiledPDFView;


@interface ZoomingPDFViewerForIPadViewController : UIViewController <UIScrollViewDelegate,PDFScrollViewDelegate>  {

 CGPDFPageRef page;



 CGPDFDocumentRef pdf;

 NSInteger currentPage;



 NSInteger pageCount;

 PDFScrollView *myScrollView;

 PDFScrollView *previousPage;
 PDFScrollView *nextPage;

}

-(id)initWithResourcePath:(NSString*)path ;
-(void)loadNextPage;
-(void)loadPreviousPage;
@end


@implementation ZoomingPDFViewerForIPadViewController

// some more code
#pragma mark -
#pragma mark PDFScrollViewDelegate methods
/* 
 called when user swipes right on the view
 */

-(void)viewDetectedRightSwipe:(PDFScrollView*)pdfScrollView withGesture:(UISwipeGestureRecognizer*)recognizer {
  NSLog(@"right");
 if (currentPage>1){
  //decreate page counter
  currentPage--;


  // release old next page

  if(nextPage){
   [nextPage release];
  }
  // set the actual page as the next one
  nextPage = [myScrollView retain];

  // remove the view from the actual view
  [myScrollView removeFromSuperview];

  // check if the previous page is loaded
  if(!previousPage)
   [self loadPreviousPage];

  // set the previouse page as the actual page
  myScrollView = previousPage;

  myScrollView.pdfViewDelegate = self;
  //[myScrollView drawPage];
  // load a new previous page
  //[NSThread detachNewThreadSelector:@selector(loadNextPage) toTarget:self withObject:nil];
  //[self loadNextPage];


 }
}
/*
 called when user swipes left on the view
 */
-(void)viewDetectedLeftSwipe:(PDFScrollView*)pdfScrollView withGesture:(UISwipeGestureRecognizer*)recognizer{
 NSLog(@"left");
 // if the end of the document isn't reached 
 if (currentPage<pageCount){
  //increment current page
  currentPage++;
  // if a previous page has been loaded release it
  if (previousPage) {
   [previousPage release];
  }
  // assing the actual view to as a previous page  and retain it before it gets release by superview
  previousPage = [myScrollView retain];
  // remove the view from the super view 
  [myScrollView removeFromSuperview];

  // if a next page hasn't beeen loaded yet, load it on this thread
  if (!nextPage)
   [self loadNextPage];

  // assign the next page as the current page
  myScrollView = nextPage;

  // put the current page the delegate
  myScrollView.pdfViewDelegate = self;

  // add the current page to the super view
  [[self view] addSubview:myScrollView];

  // load a next page.
  [NSThread detachNewThreadSelector:@selector(loadNextPage) toTarget:self withObject:nil];
 //[self loadNextPage];


 }  

}

/*
 called when the user taps the screen
 */
-(void)viewDetectedTapping:(PDFScrollView*)pdfScrollView withGesture:(UITapGestureRecognizer*)recognizer {
 NSLog(@"tapped");
 [myScrollView setZoomScale:1.0f animated:YES];


}


-(void)loadNextPage {
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 CGPDFPageRef aPage =  CGPDFDocumentGetPage(pdf, currentPage+1);
 nextPage = [[PDFScrollView alloc] initWithFrame:myScrollView.frame andPDFPage:aPage ];
 [pool release];
}



-(void)loadPreviousPage {

 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
 CGPDFPageRef aPage =  CGPDFDocumentGetPage(pdf, currentPage-1);
 previousPage = [[PDFScrollView alloc] initWithFrame:myScrollView.frame andPDFPage:aPage];
 [pool release];
}
@end

this is the code that the gestures trigger.

-(void)handleRightSwipe:(UIGestureRecognizer*)gesture {


    if ([pdfViewDelegate respondsToSelector:@selector(viewDetectedRightSwipe:withGesture:)]) {
        UISwipeGestureRecognizer *swipe = (UISwipeGestureRecognizer*)gesture;
        [pdfViewDelegate viewDetectedRightSwipe:self withGesture:swipe];

    }




}
-(void)handleLeftSwipe :(UIGestureRecognizer*)gesture{


    if ([pdfViewDelegate respondsToSelector:@selector(viewDetectedLeftSwipe:withGesture:)]) {
        UISwipeGestureRecognizer *swipe= (UISwipeGestureRecognizer*)gesture;
        [pdfViewDelegate viewDetectedLeftSwipe:self withGesture:swipe];

    }

}

Thanks in advance for your time

Was it helpful?

Solution

On iOS 4.x.x there is a bug, which caused the callback to be called twice if you removeFromSuperview inside callback.

You can set enabled property to NO in removeFromSuperview:

- (void)removeFromSuperview
{
    for(UIGestureRecognizer* gestureRecognizer in self.gestureRecognizers) {
        gestureRecognizer.enabled = NO;
    }
    [super removeFromSuperview];
}

- (void)willMoveToSuperview:(UIView *)newSuperview
{
    for(UIGestureRecognizer* gestureRecognizer in self.gestureRecognizers) {
        gestureRecognizer.enabled = YES;
    }
    [super willMoveToSuperview:newSuperview];
}

However, the callback will still fire, even if it is disabled. So you should then check enabled property in callback:

- (void)didSwipeRight:(UISwipeGestureRecognizer *)gestureRecognizer
{
    if (gestureRecognizer.enabled) {
            //do something useful...
    }
}

OTHER TIPS

I had the exact same problem and my guess is that the second action fired when this line was executed:

[myScrollView removeFromSuperview];

For some reason, UISwipeGestureRecognizer fires when the view it's attached to is removed. No other UIGestureRecognizer appears to does this.

My solution was do disable all gesture recognizers before calling removeFromSuperview:

for (UIGestureRecognizer *g in myScrollView.gestureRecognizers) {
    g.enabled = NO;
    g.delegate = nil;
}
[myScrollView removeFromSuperview];

It isn't ideal but it did the trick.

@Pacu,

Don't use a timer!

You need to be looking at the gesture state. The UIGestureRecognizer sends you info about which part of the gesture you are getting. Gestures aren't a single event. Think about a pinch gesture... it doesn't just happen once, it starts, changes position, and can be cancelled or fail or end. Each time one of those things happens your callback gets run.

Just ignoring events that happen close together will USUALLY work, but for instance a swipe gesture could last longer than your time window.

switch (sender.state) {
    case UIGestureRecognizerStateBegan:
        self.dragging = [self objectToRotateOrPinch:sender];
        break;
    case UIGestureRecognizerStateEnded:
        self.dragging = nil;
        break;
    case UIGestureRecognizerStateCancelled:
        self.dragging = nil;
        break;
    case UIGestureRecognizerStateFailed:
        self.dragging = nil;
        break;
    case UIGestureRecognizerStateChanged:
        // rotate or pinch
        break;
    default:
        break;
}

If all you care about is when a swipe has happened, you'll want to only react when the state == UIGestureRecognizerStateEnded.

Try checking the state property of the gesture before passing it to the delegate:

-(void)handleRightSwipe:(UIGestureRecognizer*)gesture {
    if (gesture.state != UIGestureRecognizerStateEnded)
        return; //gesture not finished yet

    if ([pdfViewDelegate respondsToSelector:@selector(viewDetectedRightSwipe:withGesture:)]) {
        UISwipeGestureRecognizer *swipe = (UISwipeGestureRecognizer*)gesture;
        [pdfViewDelegate viewDetectedRightSwipe:self withGesture:swipe];
    }
}

If that works, do the same with the left swipe.

The following code is a simple workaround that is, I think, unlikely to have any unwanted side effects.

- (void) leftSwipe: (UISwipeGestureRecognizer *) recognizer;
{
    if (![recognizer isEnabled]) return;    
    [recognizer setEnabled:NO];
    [recognizer performSelector:@selector(setEnabled:) withObject: [NSNumber numberWithBool:YES] afterDelay:0.1];
       // your gesture handling code here....

I came across this issue when I switched from using the notifications to receive my gestures to receiving them directly, but I've not looked further into the issue. The state property was definitely of no help to me - logging showed first and second calls provided the recognizer in the same state.

I had the same problem, seems that the problem occurs when you try to remove the view (Gesture Fires), so, rewrite removeFromSuperview... This sniplet works for me ...

- (void)removeFromSuperview
{
    for(UIGestureRecognizer* gesture in [self gestureRecognizers])
        [self removeGestureRecognizer:gesture];
    [super removeFromSuperview];
}

I had exactly the same problem and you could also send removeGestureRecognizer: message to the UIView kind of class.

-(void)handleLeftSwipe:(UIGestureRecognizer *)gesture
{
    UIView *vw = [gesture view];
    [view removeGestureRecognizer:gesture];
    [view removeFromSuperview];
}

However, I still don't know why the gesture is 'triggered' when the view is being removed from the superview.

Cheers,

the answer of aBitObvious is good when the gesture is detected as it's being "done" by the user, but It is REALLY good place to start digging into the issue. The thing here is that the gesture itself is detected once, but the action associated with it is fired twice. I did a timer to track how many milliseconds were in between those calls to ignore fake calls to the action.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top