Reputation: 1985
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
Upvotes: 4
Views: 6023
Reputation: 6081
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...
}
}
Upvotes: 2
Reputation: 2030
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,
Upvotes: 1
Reputation: 2030
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];
}
Upvotes: 1
Reputation: 7072
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.
Upvotes: 1
Reputation: 606
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.
Upvotes: 7
Reputation: 12087
@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.
Upvotes: 5
Reputation: 1985
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.
Upvotes: -1
Reputation:
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.
Upvotes: 1