Question

I have three view controllers that are part of a UIScrollView. I want to be able to swipe between the three, although one of the view controllers has a UIPanGestureRecognizer. I use this pan gesture recognizer to allow the user to drag their finger up and down to increase and decrease the height of a rectangular UIView. Therefore, this UIPanGestureRecognizer only really needs to know about the upwards/downwards panning, and the scroll view can use the horizontal panning.

An example of this, is like the home screen; you can swipe left or right, but also swipe down to get spotlight. I want this kind of mechanism.

This is my code for the pan:

- (void)pan:(UIPanGestureRecognizer *)aPan; // When pan guesture is recognised
{
CGPoint location = [aPan locationInView:self.view]; // Location of finger on screen
CGRect secondRect = CGRectMake(210.0, 45.0, 70.0, 325.0); // Rectangles of maximimum bar area
CGRect minuteRect = CGRectMake(125.0, 45.0, 70.0, 325.0);
CGRect hourRect = CGRectMake(41.0, 45.0, 70.0, 325.0);

if (CGRectContainsPoint(secondRect, location)) { // If finger is inside the 'second' rectangle

    CGPoint currentPoint = [aPan locationInView:self.view];

    currentPoint.y -= 80;  // Make sure animation doesn't go outside the bars' rectangle

    if (currentPoint.y < 0) {
    currentPoint.y = 0;
    }
    else if (currentPoint.y > 239) {
    currentPoint.y = 239;
    }

    currentPoint.y = 239.0 - currentPoint.y;

    CGFloat pointy = currentPoint.y - fmod(currentPoint.y, 4.0);

    [UIView animateWithDuration:0.01f  // Animate the bars to rise as the finger moves up and down
                     animations:^{
                         CGRect oldFrame = secondBar.frame;
                         secondBar.frame = CGRectMake(oldFrame.origin.x, (oldFrame.origin.y - (pointy - secondBar.frame.size.height)), oldFrame.size.width, (pointy));
                     }];

    CGFloat result = secondBar.frame.size.height - fmod(secondBar.frame.size.height, 4.0);

    secondInt = (result / 4.0); // Update labels with new time

    self->secondLabel.text = [NSString stringWithFormat:@"%02d", secondInt];
}

The code is basically repeated for three separate rectangular UIViews.

If anyone can tell me how to get the homescreen-style panning/swiping into my app, that would be great!!

Was it helpful?

Solution

Alright, here is the short answer:

You have to use UIGestureRecognizer's method -requireGestureRecognizerToFail:.

And here is the long answer:

You have to make the pan gesture recognizer of your scroll view to succeed only if the pan gesture recognizer of TimerViewController fails. However that gesture (TimerViewController's gesture) should only succeed if the initial movement is vertical. If it is horizontal it should fail. To accomplish this we have to subclass UIPanGestureRecognizer and modify it to fit those needs. Here is what you have to do:

  1. Disregard ALL the changes you made from my previous answer
  2. Add VerticalPanGestureRecognizer to your project.
  3. Modify TimerViewController as shown.
  4. Modify ScrollViewController as shown.

VerticalPanGestureRecognizer.h

#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>

@interface VerticalPanGestureRecognizer : UIPanGestureRecognizer

@end

VerticalPanGestureRecognizer.m

#import "VerticalPanGestureRecognizer.h"

@interface VerticalPanGestureRecognizer ()

@property (nonatomic, assign) CGPoint origLoc;

@end

@implementation VerticalPanGestureRecognizer

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    self.origLoc = [[touches anyObject] locationInView:self.view.superview];
    [super touchesBegan:touches withEvent:event];
}

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

    if (self.state == UIGestureRecognizerStatePossible) {
        CGPoint loc = [[touches anyObject] locationInView:self.view.superview];
        CGFloat deltaX = fabs(loc.x - self.origLoc.x);
        CGFloat deltaY = fabs(loc.y - self.origLoc.y);
        if (deltaY < deltaX)
            self.state = UIGestureRecognizerStateFailed;
    }

    [super touchesMoved:touches withEvent:event];
}

@end

TimerViewController.h

// Your imports here

@interface TimerViewController : UIViewController

{
    // Your ivars here
}

// Add the following property
@property (nonatomic, strong) UIPanGestureRecognizer *pan;

// Your methods here

@end

TimerViewController.m

#import "TimerViewController.h"
#import "VerticalPanGestureRecognizer.h"

@implementation TimerViewController
@synthesize pan = _pan;

// prefersStatusBarHidden method here

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil // Initialise view controller
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];

    if (self) {

        // Instantiate the pan gesture as "VerticalPanGestureRecognizer"
        self.pan = [[VerticalPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]; // Create recogniser for a pan guesture
        self.pan.maximumNumberOfTouches = self.pan.minimumNumberOfTouches = 1;
        [self.view addGestureRecognizer:self.pan];
    }

    return self;
}

// The rest of your code here

@end

ScrollViewController.m

- (void)viewDidLoad
{
    // Your code here

    TimerViewController *tvc = [[TimerViewController alloc]init];
    CGRect frame = tvc.view.frame;
    frame.origin.x = 320;
    tvc.view.frame = frame;

    // Add the following line 
    [self.scrollView.panGestureRecognizer requireGestureRecognizerToFail:tvc.pan];

    [self addChildViewController:tvc];
    [self.scrollView addSubview:tvc.view];
    [tvc didMoveToParentViewController:self];

    // More code here
}

This new approach works perfectly. I tested it. Let me know if you have more questions.

Cheers!

UPDATE

To answer the question you posted on the comments, here is what you have to do:

- (void)viewDidLoad
{
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

    BarsViewController *bvc = [[BarsViewController alloc]init];
    [self addChildViewController:bvc];
    [self.scrollView addSubview:bvc.view];
    [bvc didMoveToParentViewController:self];

    TimerViewController *tvc = [[TimerViewController alloc]init];
    CGRect frame = tvc.view.frame;
    frame.origin.x = 320;
    tvc.view.frame = frame;

    [self.scrollView.panGestureRecognizer requireGestureRecognizerToFail:tvc.pan];
    [self addChildViewController:tvc];
    [self.scrollView addSubview:tvc.view];
    [tvc didMoveToParentViewController:self];

    StopwatchViewController *svc = [[StopwatchViewController alloc] init];
    frame = svc.view.frame;
    frame.origin.x = 320*2;
    svc.view.frame = frame;

    [self addChildViewController:svc];
    [self.scrollView addSubview:svc.view];
    [svc didMoveToParentViewController:self];

    self.scrollView.contentSize = CGSizeMake(320*3, self.view.frame.size.height);
    self.scrollView.pagingEnabled = YES;

    [self.scrollView setShowsHorizontalScrollIndicator:NO];
}

Again, I tested it and it's working. You just have to add the gesture recognizer for the bars

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