UIPanGestureRecognizer interfering with scroll - ios

I have a scroll view with 3 view controllers that I want to swipe between. However, one of my view controllers has a UIPanGestureRecognizer and this stops the scrollview from working. This is the 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];
}
}
Basically, this code allows the user to drag their finger up and down the screen, and change the height of a uiview. Therefore, I only need the up/down pan gesture for this and only the left/right pan gestures for the scroll view.
Does anyone have any idea how I can do this?

Your scrollview has a property called panGestureRecognizer. You can create a delegate for your own UIPanGestureRecognizer, and implement this method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if(otherGestureRecognizer == myScrollView.panGestureRecognizer) {
return YES;
} else {
return NO;
}
}

UIPanGestureRecognizer contains a delegate method - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch that you can return YES or NO to let/prevent the pan gesture from receiving the touch.
Within that method, you could write something like:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
CGPoint velocity = [gestureRecognizer velocityInView:yourView];
if(velocity.x > 0)
{
NSLog(#"gesture went right"); // return NO
}
else if (velocity.x < 0)
{
NSLog(#"gesture went left"); // return NO
}
if (velocity.y > 0)
{
NSLog(#"gesture went down"); // return YES
}
else if (velocity.y < 0)
{
NSLog(#"gesture went up"); // return YES
}
}
Admittedly, that probably isn't the cleanest implementation. You're leaving an open door for mixed gestures. So just make sure to watch for that.

Related

iOS: Resize and Rotate UIView Concurrently

Using a UIPanGestureRecognizer in my view controller, I'm trying to draw a view (ArrowView) at an angle based upon the touch location. I'm trying to use CGAffineTransformRotate to rotate the view based up the angle between the first touch and the current touch, but this isn't working unless the view has already been drawn by at lease 20 or more pixels. Also, when drawing, the view doesn't always line up under my finger. Is this the correct approach for this situation? If not, does anyone recommend a better way of accomplishing this? If so, what am I doing wrong?
ViewController.m
#implementation ViewController {
ArrowView *_selectedArrowView;
UIColor *_selectedColor;
CGFloat _selectedWeight;
CGPoint _startPoint;
}
- (void)viewDidLoad {
[super viewDidLoad];
_selectedColor = [UIColor yellowColor];
_selectedWeight = 3;
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panHandler:)];
[self.view addGestureRecognizer:pan];
}
- (void) panHandler: (UIPanGestureRecognizer *) sender {
if (sender.state == UIGestureRecognizerStateBegan) {
//Instantiate the arrow
CGPoint touchPoint = [sender locationInView:sender.view];
_startPoint = touchPoint;
_selectedArrowView = [[ArrowView alloc] initWithFrame:CGRectMake(touchPoint.x, touchPoint.y, 0, 25) withColor:_selectedColor withWeight:_selectedWeight];
_selectedArrowView.delegate = self;
[self.view addSubview:_selectedArrowView];
[self.view bringSubviewToFront:_selectedArrowView];
} else if (sender.state == UIGestureRecognizerStateChanged) {
//"Draw" the arrow based upon finger postion
CGPoint touchPoint = [sender locationInView:sender.view];
[_selectedArrowView drawArrow:_startPoint to:touchPoint];
}
}
#end
ArrowView.m
- (void) drawArrow: (CGPoint) startPoint to: (CGPoint) endPoint {
startPoint = [self convertPoint:startPoint fromView:self.superview];
endPoint = [self convertPoint:endPoint fromView:self.superview];
if (_initialAngle == -1000 /*Initially set to an arbitrary value so I know when the draw began*/) {
_initialAngle = atan2(startPoint.y - endPoint.y, startPoint.x - endPoint.x);
[self setPosition:0];
} else {
CGFloat ang = atan2(startPoint.y - endPoint.y, startPoint.x - endPoint.x);
ang -= _initialAngle;
self.transform = CGAffineTransformRotate(self.transform, ang);
CGFloat diff = (endPoint.x - self.bounds.size.width);
NSLog(#"\n\n diff: %f \n\n", diff);
self.bounds = CGRectMake(0, 0, self.bounds.size.width + diff, self.bounds.size.height);
_endPoint = CGPointMake(self.bounds.size.width, self.bounds.size.height);
[self setNeedsDisplay];
}
}
- (void) setPosition: (CGFloat) anchorPointX {
CGPoint layerLoc;
if (anchorPointX == 0) {
layerLoc = CGPointMake(self.layer.bounds.origin.x, self.layer.bounds.origin.y + (self.layer.bounds.size.height / 2));
} else {
layerLoc = CGPointMake(self.layer.bounds.origin.x + self.layer.bounds.size.width, self.layer.bounds.origin.y + (self.layer.bounds.size.height / 2));
}
CGPoint superLoc = [self convertPoint:layerLoc toView:self.superview];
self.layer.anchorPoint = CGPointMake(anchorPointX, 0.5);
self.layer.position = superLoc;
}
- (CGFloat) pToA: (CGPoint) touchPoint {
CGPoint start;
if (_dotButtonIndex == kDotButtonFirst) {
start = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));
} else {
start = CGPointMake(CGRectGetMinX(self.bounds), CGRectGetMinY(self.bounds));
}
return atan2(start.y - touchPoint.y, start.x - touchPoint.x);
}
Link to project on GitHub: Project Link
Figured it out.
I had to make it have an initial width so the angle would work.
_initialAngle = atan2(startPoint.y - endPoint.y, startPoint.x - (endPoint.x + self.frame.size.width));

view slide (follow finger position vertically up/down) with uipangesturerecognizer and auto layout

this is a very simple problem but i can't figure it out. first, here is how the views are added as sub view and I'm using Autolayout. the (black) root view contains blue colored and orange coloured views. the orange coloured view is the tops most which partly covers the blue view. the button in orange view has pan gesture recognizer. i got it working if gestures ends then orange view positions itself properly.( either just like in the photo covering the blue view, or orange can be slided down till only the orange is visible.) the orange view's position is only changed vertically using autolayout constant.
the issue I'm facing is that if one pans up and down UIGestureRecognizerStateChanged then it happens that the orange view slides to the opposite direction of panning. how to change the auto layout constant properly? the orange view's range of vertical movement is from starting point like in the sketch downward till that orange button remains visible.
#define MIN_SIZE 50
-(void)handlePan:(UIPanGestureRecognizer *) pan
{
CGFloat viewHeight = pan.view.superview.height; //superView is the big orange view
if (pan.state == UIGestureRecognizerStateBegan)
{
self.startLocation = [pan locationInView:self.view];
}
if (pan.state == UIGestureRecognizerStateChanged)
{
CGPoint stopLocation = [pan locationInView:self.view];
CGFloat dist = sqrt(pow((stopLocation.x - self.startLocation.x), 2) + pow((stopLocation.y - self.startLocation.y), 2));
if (velocityOfPan.y > 0)
{
if (self.verticalConstraint.constant >= viewHeight - MIN_SIZE)
{
self.verticalConstraint.constant = viewHeight - MIN_SIZE;
}
else
self.verticalConstraint.constant = dist;
}
else if(velocityOfPan.y < 0)
{
if (self.verticalConstraint.constant <= 0)
{
self.verticalConstraint.constant = 0;
}
else
{
self.verticalConstraint.constant = (viewHeight - MIN_SIZE - dist);
}
}
}
else if(pan.state == UIGestureRecognizerStateEnded)
{
if (velocityOfPan.y > 0)
{
self.verticalConstraint.constant = viewHeight - MIN_SIZE;
}
else if (velocityOfPan.y < 0)
{
self.verticalConstraint.constant = 0;
}
}
[self updateViewAnimation];
}
}
-(void) updateViewAnimation
{
[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:0.5 delay:0 usingSpringWithDamping:0.6 initialSpringVelocity:-1 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
after some searching and reading I found out what I was doing wrong. Here is how I solved it, just in case somebody else wanna know it.
-(void)(UIPanGestureRecognizer *) pan
{
CGPoint velocityOfPan = [pan velocityInView:self.view];
CGFloat viewHeight = pan.view.superview.height;
CGPoint delta;
switch(pan.state)
{
case UIGestureRecognizerStateBegan:
self.startLocation = delta = [pan translationInView:self.view];
break;
case UIGestureRecognizerStateChanged:
{
delta = CGPointApplyAffineTransform([pan translationInView:self.view], CGAffineTransformMakeTranslation(-self.startLocation.x, -self.startLocation.y));
self.startLocation = [pan translationInView:self.view];
if(delta.y < 0 && self.verticalConstraint.constant < 0)
{
delta.y = 0;
}
else
{
delta.y = self.verticalConstraint.constant + delta.y;
}
self.verticalConstraint.constant = delta.y;
[self updateViewAnimation];
break;
}
case UIGestureRecognizerStateEnded:
{
if (velocityOfPan.y > 0)
{
delta.y = (viewHeight - MIN_SIZE);
}
else if(velocityOfPan.y < 0)
{
delta.y = 0;
}
self.verticalConstraint.constant = delta.y;
[self updateViewAnimation];
break;
}
default:
break;
}
}

UIcollectionview cell animation effect that moves the collection frame horizontally

I was wondering if someone can point me in the right direction. I have created a UIcollectionview that scrolls horizontally (one line). If a user scroll very fast, I would like the last cell to pull with it the collection frame showing what is behind the frame. What I do not want is for the last cell to move from its place and should the collection background. I added a pan gesture to the collection view, and with the logic I put in place, I see that the collection frame moves before the last cell is reached. same if you scroll backward to reach the first cell. I am new at this and there got to be a easy way to do it.
- (IBAction)handleGesture:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x/3,recognizer.view.center.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
if(recognizer.state == UIGestureRecognizerStateEnded)
{
[UIView animateWithDuration:0.6 delay:0 options:UIViewAnimationOptionAllowUserInteraction|UIViewAnimationOptionBeginFromCurrentState animations:^{
recognizer.view.center = CGPointMake(self.view.frame.size.width/2, recognizer.view.center.y);
} completion:nil];
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([gestureRecognizer isEqual:self.panGesture] && [otherGestureRecognizer isEqual:self.collectionview.panGestureRecognizer]){
return YES;
}
return NO;
}
- (BOOL) gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if([gestureRecognizer isEqual:self.panGesture])
{
if(self.collectionview.contentOffset.x ==0)
{
return YES;
}
else if(self.collectionview.contentOffset.x >= (self.collectionview.contentSize.width - self.view.bounds.size.width))
{
return YES;
}
}
return NO;
}

Rotation of view while dragging

I want to slightly rotate a UIView when it's being dragged. I managed to do the dragging using a UIPanGestureRecognizer. I also tried to Rotate using CGAffineTransformRotate and it worked. However, when i apply both dragging and rotation, the UIView rotates only and it doesn't get dragged around the super view. Here is what i'm doing in the gesture handler:
CGPoint translation = [gesture translationInView:self.view];
// to drag
gesture.view.center = CGPointMake(gesture.view.center.x + translation.x, gesture.view.center.y + translation.y);
// to rotate
gesture.view.transform = CGAffineTransformRotate(gesture.view.transform, degreesToRadians(translation.x)/4);
// return to default so that it doesn't accumulate
[gesture setTranslation:CGPointMake(0, 0) inView:self.view];
How can i fix this ?
EDIT:
here is the entire code:
- (void)handlePan:(UIPanGestureRecognizer*)gesture{
if (gesture.state == UIGestureRecognizerStateBegan) {
// Save initial post center for snapping
point = CGPointMake(gesture.view.center.x, gesture.view.center.y);
} else if (gesture.state == UIGestureRecognizerStateChanged){
// Translate user movement across the screen to dragging coordinates
CGPoint translation = [gesture translationInView:self.view];
gesture.view.center = CGPointMake(gesture.view.center.x + translation.x, gesture.view.center.y + translation.y);
CGAffineTransform rotate = CGAffineTransformRotate(gesture.view.transform, degreesToRadians(translation.x)/4);
gesture.view.transform = rotate;//CGAffineTransformConcat(translate, rotate);
[gesture setTranslation:CGPointMake(0, 0) inView:self.view];
} else if (gesture.state == UIGestureRecognizerStateEnded){
// Animate snap back to place
[UIView animateWithDuration:0.2 animations:^{
gesture.view.transform = CGAffineTransformIdentity;
// [self.imagePost setCenter:point];
}];
}
}
Try adding this method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (![gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] && ![otherGestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
return YES;
}
return NO;
}

Draggable UITextField enters editing when it shouldn't

I'm developing an iOS6 app for iPad. I have coded a subclass of UITextField that enables the user to drag, pinch and rotate the field around the view. The problem is that if you pinch the field with no rotation, after finishing the gesture, it enters in editing mode. This should not happen, and I've added some lines that disable that, but it doesn't stop. Here I my code:
- (BOOL) canBecomeFirstResponder{
if (gesturing==YES) {
return NO;
}else return YES;
}
- (void) tapDetected:(UITapGestureRecognizer*) pinchRecognizer {
if (gesturing==NO) {
[self becomeFirstResponder];
}
}
- (void) pinchDetected:(UIPinchGestureRecognizer*) pinchRecognizer {
CGFloat scale = pinchRecognizer.scale;
self.font = [self.font fontWithSize:self.font.pointSize*(scale)];
//self.transform = CGAffineTransformScale(self.transform, scale, scale);
[self sizeToFit];
pinchRecognizer.scale = 1.0;
if (pinchRecognizer.state == UIGestureRecognizerStateChanged) {
gesturing =YES;
}
if (pinchRecognizer.state == UIGestureRecognizerStateBegan) {
gesturing =YES;
}
if (pinchRecognizer.state == UIGestureRecognizerStateEnded) {
gesturing =NO;
}
}
- (void) panDetected:(UIPanGestureRecognizer *)panRecognizer {
CGPoint translation = [panRecognizer translationInView:self.superview];
CGPoint imageViewPosition = self.center;
imageViewPosition.x += translation.x;
imageViewPosition.y += translation.y;
self.center = imageViewPosition;
[panRecognizer setTranslation:CGPointZero inView: self.superview];
if (panRecognizer.state == UIGestureRecognizerStateChanged) {
gesturing=YES;
}
if (panRecognizer.state == UIGestureRecognizerStateEnded) {
gesturing=NO;
}
}
- (void) rotationDetected:(UIRotationGestureRecognizer *)rotationRecognizer{
CGFloat angle = rotationRecognizer.rotation;
self.transform = CGAffineTransformRotate(self.transform, angle);
rotationRecognizer.rotation = 0.0;
if (rotationRecognizer.state == UIGestureRecognizerStateChanged) {
gesturing = YES;
}
if (rotationRecognizer.state == UIGestureRecognizerStateEnded) {
gesturing = NO;
}
}
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
I bydefault value of gesturing is NO, if you don't initialize it to YES. Have debugged and figured out which gesture recognizer is detected first whenever you try to apply pinch?
You support simultaneous gesture recognition and hence when you put to apply pinch, your finger's touch may get detected as pan. If it is the case then if(gesturing == NO) condition in tapDetected: method gets satisfied and your textField becomes first responder.

Resources