Scrolling with two fingers with a UIScrollView

后端 未结 14 604
长发绾君心
长发绾君心 2020-11-29 21:29

I have an app where my main view accepts both touchesBegan and touchesMoved, and therefore takes in single finger touches, and drags. I want to im

相关标签:
14条回答
  • 2020-11-29 22:11

    Yes, you'll need to subclass UIScrollView and override its -touchesBegan: and -touchesEnded: methods to pass touches "up". This will probably also involve the subclass having a UIView member variable so that it knows what it's meant to pass the touches up to.

    0 讨论(0)
  • 2020-11-29 22:12

    This answers are a mess since you can only find the correct answer by reading all the other answers and the comments (closest answer got the question backwards). The accepted answer is too vague to be useful, and suggests a different method.

    Synthesizing, this works

        // makes it so that only two finger scrolls go
        for (id gestureRecognizer in self.gestureRecognizers) {     
            if ([gestureRecognizer  isKindOfClass:[UIPanGestureRecognizer class]])
            {
                UIPanGestureRecognizer *panGR = gestureRecognizer;
                panGR.minimumNumberOfTouches = 2;              
                panGR.maximumNumberOfTouches = 2;
            }
        }   
    

    This requires two fingers for a scroll. I've done this in a subclass, but if not, just replace self.gestureRecognizers with myScrollView.gestureRecognizers and you're good to go.

    The only thing that I added is using id to avoid an ugly cast :)

    This works but can get quite messy if you want your UIScrollView to do zoom too... the gestures don't work correctly, since pinch-to-zoom and scroll fight it out. I'll update this if I find a suitable answer.

    0 讨论(0)
  • 2020-11-29 22:15

    You need to subclass UIScrollView (of course!). Then you need to:

    • make single-finger events to go to your content view (easy), and

    • make two-finger events scroll the scroll view (may be easy, may be hard, may be impossible).

    Patrick's suggestion is generally fine: let your UIScrollView subclass know about your content view, then in touch event handlers check the number of fingers and forward the event accordingly. Just be sure that (1) the events you send to content view don't bubble back to UIScrollView through the responder chain (i.e. make sure to handle them all), (2) respect the usual flow of touch events (i.e. touchesBegan, than some number of {touchesBegan, touchesMoved, touchesEnded}, finished with touchesEnded or touchesCancelled), especially when dealing with UIScrollView. #2 can be tricky.

    If you decide the event is for UIScrollView, another trick is to make UIScrollView believe your two-finger gesture is actually a one-finger gesture (because UIScrollView cannot be scrolled with two fingers). Try passing only the data for one finger to super (by filtering the (NSSet *)touches argument — note that it only contains the changed touches — and ignoring events for the wrong finger altogether).

    If that does not work, you are in trouble. Theoretically you can try to create artificial touches to feed to UIScrollView by creating a class that looks similar to UITouch. Underlying C code does not check types, so maybe casting (YourTouch *) into (UITouch *) will work, and you will be able to trick UIScrollView into handling the touches that did not really happen.

    You probably want to read my article on advanced UIScrollView tricks (and see some totally unrelated UIScrollView sample code there).

    Of course, if you can't get it to work, there's always an option of either controlling UIScrollView's movement manually, or use an entirely custom-written scroll view. There's TTScrollView class in Three20 library; it does not feel good to the user, but does feel good to programmer.

    0 讨论(0)
  • 2020-11-29 22:15

    I've got a further improvement to the code above. The problem was, that even after we set setCanCancelContentTouches:NO We have the problem, that a zoom gesture will interrupt with the content. It won't cancel the content touch but allow zooming in the meantime. TO prevent this i lock the zooming by setting the minimumZoomScale and maximumZoomScale to the same values everytime, the timer fires.

    A quite strange behavior is that when a one finger event gets canceled by a two finger gesture within the allowed time period, the timer will be delayed. It gets fired after the touchCanceled Event gets called. So we have the problem, that we try to lock the zooming although the event is already canceled and therefore disable zooming for the next event. To handle this behavior the timer callback method checks against if touchesCanceled was called before. @implementation JWTwoFingerScrollView

    #pragma mark -
    #pragma mark Event Passing
    
    
    - (id)initWithCoder:(NSCoder *)coder {
        self = [super initWithCoder:coder];
        if (self) {
            for (UIGestureRecognizer* r in self.gestureRecognizers) {
                if ([r isKindOfClass:[UIPanGestureRecognizer class]]) {
                    [((UIPanGestureRecognizer*)r) setMaximumNumberOfTouches:2];
                    [((UIPanGestureRecognizer*)r) setMinimumNumberOfTouches:2];
                    zoomScale[0] = -1.0;
                    zoomScale[1] = -1.0;
                }
                timerWasDelayed = NO;
            }
        }
        return self;
    }
    -(void)lockZoomScale {    
        zoomScale[0] = self.minimumZoomScale;
        zoomScale[1] = self.maximumZoomScale;
        [self setMinimumZoomScale:self.zoomScale];
        [self setMaximumZoomScale:self.zoomScale];
            NSLog(@"locked %.2f %.2f",self.minimumZoomScale,self.maximumZoomScale);
    }
    -(void)unlockZoomScale {
        if (zoomScale[0] != -1 && zoomScale[1] != -1) {
            [self setMinimumZoomScale:zoomScale[0]];
            [self setMaximumZoomScale:zoomScale[1]];
            zoomScale[0] = -1.0;
            zoomScale[1] = -1.0;
            NSLog(@"unlocked %.2f %.2f",self.minimumZoomScale,self.maximumZoomScale);
        }
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        NSLog(@"began %i",[event allTouches].count);
        [self setCanCancelContentTouches:YES];
         if ([event allTouches].count == 1){
             touchesBeganTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(firstTouchTimerFired:) userInfo:nil repeats:NO];
             [touchesBeganTimer retain];
             [touchFilter touchesBegan:touches withEvent:event];
         }
     }
    
    //if one finger touch gets canceled by two finger touch, this timer gets delayed
    // so we can! use this method to disable zooming, because it doesnt get called when two finger touch events are wanted; otherwise we would disable zooming while zooming
    -(void)firstTouchTimerFired:(NSTimer*)timer {
        NSLog(@"fired");
        [self setCanCancelContentTouches:NO];
        //if already locked: unlock
        //this happens because two finger gesture delays timer until touch event finishes.. then we dont want to lock!
        if (timerWasDelayed) {
            [self unlockZoomScale];
        }
        else {
            [self lockZoomScale];
        }
        timerWasDelayed = NO;
     }
    
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    //    NSLog(@"moved %i",[event allTouches].count);
        [touchFilter touchesMoved:touches withEvent:event];
    }
    
     - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
        NSLog(@"ended %i",[event allTouches].count);
        [touchFilter touchesEnded:touches withEvent:event];
        [self unlockZoomScale];
     }
    
     //[self setCanCancelContentTouches:NO];
     -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
        NSLog(@"canceled %i",[event allTouches].count);
        [touchFilter touchesCancelled:touches withEvent:event];
        [self unlockZoomScale];
         timerWasDelayed = YES;
     }
    
    @end
    

    0 讨论(0)
  • 2020-11-29 22:19

    For iOS 5+, setting this property has the same effect as the answer by Mike Laurence:

    self.scrollView.panGestureRecognizer.minimumNumberOfTouches = 2;
    

    One finger dragging is ignored by panGestureRecognizer and so the one finger drag event gets passed to the content view.

    0 讨论(0)
  • 2020-11-29 22:24

    we managed to implement similar functionality in our iPhone drawing app by subclassing UIScrollView and filtering events depending on number of touches in simple and rude way:

    //OCRScroller.h
    @interface OCRUIScrollView: UIScrollView
    {
        double pass2scroller;
    }
    @end
    
    //OCRScroller.mm
    @implementation OCRUIScrollView
    - (id)initWithFrame:(CGRect)aRect {
        pass2scroller = 0;
        UIScrollView* newv = [super initWithFrame:aRect];
        return newv;
    }
    - (void)setupPassOnEvent:(UIEvent *)event {
        int touch_cnt = [[event allTouches] count];
        if(touch_cnt<=1){
            pass2scroller = 0;
        }else{
            double timems = double(CACurrentMediaTime()*1000);
            pass2scroller = timems+200;
        }
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        [self setupPassOnEvent:event];
        [super touchesBegan:touches withEvent:event];
    }
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
        [self setupPassOnEvent:event];
        [super touchesMoved:touches withEvent:event];   
    }
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
        pass2scroller = 0;
        [super touchesEnded:touches withEvent:event];
    }
    
    
    - (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view
    {
        return YES;
    }
    
    - (BOOL)touchesShouldCancelInContentView:(UIView *)view
    {
        double timems = double(CACurrentMediaTime()*1000);
        if (pass2scroller == 0 || timems> pass2scroller){
            return NO;
        }
        return YES;
    }
    @end
    

    ScrollView setuped as follows:

    scroll_view = [[OCRUIScrollView alloc] initWithFrame:rect];
    scroll_view.contentSize = img_size;
    scroll_view.contentOffset = CGPointMake(0,0);
    scroll_view.canCancelContentTouches = YES;
    scroll_view.delaysContentTouches = NO;
    scroll_view.scrollEnabled = YES;
    scroll_view.bounces = NO;
    scroll_view.bouncesZoom = YES;
    scroll_view.maximumZoomScale = 10.0f;
    scroll_view.minimumZoomScale = 0.1f;
    scroll_view.delegate = self;
    self.view = scroll_view;
    

    simple tap does nothing (you can handle it in the way you need), tap with two fingers scrolls/zooms view as expected. no GestureRecognizer is used, so works from iOS 3.1

    0 讨论(0)
提交回复
热议问题