How can i write custom gesture for increase the size of the view in all the direction (i.e) in origin side and size of the view.
Example:
the user should touch the view in any one of the corner. either near to origin side or view end side.
Example: View frame size is (100,100,200,200). and minimum touch distance is 15 pixel from any side.
so, if the user touch location is (X=15,Y=40) in
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
method means that touch near to the origin side.
same if the user touch location is (X=15, Y=190) in
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
method means the touch is near to the size side.
My code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
if([touches count] != self.touchMinimumCount ||
[[touches anyObject] tapCount] > self.tapMinimumCount)
{
self.state = UIGestureRecognizerStateFailed;
return;
}
self.startPoint = [[touches anyObject] locationInView:self.view];
self.viewFrame = self.view.frame;
if(((self.startPoint.x - self.view.bounds.origin.x) <= self.minimumTouchDistance) ||
((self.startPoint.y - self.view.bounds.origin.y) <= self.minimumTouchDistance) ||
((ABS(self.startPoint.x - self.view.bounds.size.width)) <= self.minimumTouchDistance) ||
((ABS(self.startPoint.y - self.view.bounds.size.height)) <= self.minimumTouchDistance))
{
self.state = UIGestureRecognizerStatePossible;
if(((self.startPoint.x - self.view.bounds.origin.x) <= self.minimumTouchDistance) ||
((self.startPoint.y - self.view.bounds.origin.y) <= self.minimumTouchDistance) )
{
self.currentTouchPosition = touchPositionIsOrigin;
}
else if(((ABS(self.startPoint.x - self.view.bounds.size.width)) <= self.minimumTouchDistance) ||
((ABS(self.startPoint.y - self.view.bounds.size.height)) <= self.minimumTouchDistance))
{
self.currentTouchPosition = touchPositionIsSize;
}
}
else
{
self.state = UIGestureRecognizerStateFailed;
}
}
My gesture possible state setting if the touch in side the touch distance only.
Now i want to resize the view frame according to the user finger move.
if the user touch the origin side and move towards the left direction means, want to change the origin.x of the view and the width also want to increase.
if the user touch the origin side and move towards the top direction means, want to change the origin.y of the view and the width also want to increase.
if the user touch the origin side and move towards the right direction means, want to change the origin.x of the view.
if the user touch the origin side and move towards the bottom direction means, want to change the origin.y of the view.
if the user touch the size side and move towards the left direction means, want to reduce the width of the view.
if the user touch the size side and move towards the top direction means, want to reduce the height of the view.
if the user touch the size side and move towards the right direction means, want to increase the width of the view.
if the user touch the size side and move towards the bottom direction means, want to increase the height of the view.
this all change according tho the user finger in that view while moving.
My code:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
if(self.state == UIGestureRecognizerStateFailed) return;
direction currentDirection;
CGPoint currentLocation = [[touches anyObject] locationInView:self.view];
if(self.state == UIGestureRecognizerStatePossible ||
self.state == UIGestureRecognizerStateChanged)
{
if((currentLocation.x - self.startPoint.x) > 0)
{
currentDirection = directionRight;
self.state = UIGestureRecognizerStateChanged;
if(self.currentTouchPosition == touchPositionIsOrigin)
{
distanceInOriginX = // need to implement.
distanceInSizeX = // need to implement.
}
else if(self.currentTouchPosition == touchPositionIsSize)
{
distanceInSizeX = // need to implement.
}
}
else if((currentLocation.x - self.startPoint.x) < 0)
{
currentDirection = directionLeft;
self.state = UIGestureRecognizerStateChanged;
if(self.currentTouchPosition == touchPositionIsOrigin)
{
distanceInOriginX = // need to implement.
distanceInSizeX = // need to implement.
}
else if(self.currentTouchPosition == touchPositionIsSize)
{
distanceInSizeX = // need to implement.
}
}
else if((currentLocation.y - self.startPoint.y) >= 0)
{
currentDirection = directionBottom;
self.state = UIGestureRecognizerStateChanged;
if(self.currentTouchPosition == touchPositionIsOrigin)
{
distanceInOriginY = // need to implement.
distanceInSizeY = // need to implement.
}
else if(self.currentTouchPosition == touchPositionIsSize)
{
distanceInSizeY = // need to implement.
}
}
else if((currentLocation.y - self.startPoint.y) < 0)
{
currentDirection = directionTop;
self.state = UIGestureRecognizerStateChanged;
if(self.currentTouchPosition == touchPositionIsOrigin)
{
distanceInOriginY = // need to implement.
distanceInSizeY = // need to implement.
}
else if(self.currentTouchPosition == touchPositionIsSize)
{
distanceInSizeY = // need to implement.
}
}
else
{
currentDirection = directionUnknown;
}
self.originPoint = CGPointMake(distanceInOriginX, distanceInOriginY);
self.sizePoint = CGPointMake(distanceInSizeX, distanceInSizeY);
}
}
i mentioned the // need to implement. where ever i want the code for to achieve the functionality which i mentioned in that points. so, kindly help to go ahed.
if my was is wrong means, share the best way to do the same type of gesture.
any example is there also kindly share here. thanks in advance....
If you want to scale the size of a view, then use UIPinchGestureRecognizer. An example code is given here
UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:#selector(scale:)];
pinchRecognizer.delegate = self;
[self addGestureRecognizer:pinchRecognizer];
handler method is
#pragma mark - Gesture handling functions
-(void)scale:(UIPinchGestureRecognizer*)recognizer {
if ([recognizer state] == UIGestureRecognizerStateBegan) {
_lastScale = 1.0;
}
CGFloat scale = 1.0 - (_lastScale - [recognizer scale]);
CGAffineTransform currentTransform = self.imgView.transform;
CGAffineTransform newTransform = CGAffineTransformScale(currentTransform, scale, scale);
self.imgView.transform = newTransform;
_lastScale = [recognizer scale];
}
Related
I am trying to create a view with multiple UIButtons where any tap would be registered as a tap on the nearest UIButton. I have been unsuccessful so far, and I am hoping you guys/gals have some knowledge on standard approaches/methods used to do what I am trying to do.
You could use the Touches Began method:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Get the CGPoint of the touch:
UITouch touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
// Loop through an array containing your buttons and see if we tapped
// one of these buttons, if so, do nothing and let the button handle the tap
for (UIButton button in MyButtonsArray){
if(CGRectContainsPoint(button.frame, touchPoint)) {
return;
}
}
// if the loop completes without returning, you tapped something other than a button
// so add code here that uses the mathematical distance formula to
// calculate which button is the closest to the tap point.
}
You have all the necessary "data" in terms of coordinates of the button and coordinates of the touch point to calculate the closest button to the touch point.
that's why I am assuming that simply passing the touch to the nearest UIButton would be more practical.
No, you should place rectangle buttons next to each other and do the hit testing to determine if the touch was within the irregular shaped area.
See my blog post Hit Testing Done Right how to test with a shaped layer. There it uses a UIView, but as UIButton inherits from UIView, it would work the same, if you subclass like
#interface MyButton : UIButton
#implementation MyButton
//
// ...
//
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGPoint p = [self convertPoint:point toView:[self superview]];
if(self.layer.mask){ // a layer's mask is found. Mask can be any CGPath
if (CGPathContainsPoint([(CAShapeLayer *)self.layer.mask path], NULL, p, YES) )
return YES;
}else {
if(CGRectContainsPoint(self.layer.frame, p))
return YES;
}
return NO;
}
#end
If you want to work with partially transparent images, have a look at OBShapedButton, where alpha of a certain threshold will determine if the button was hit or not. But as mine path based approach it does so by overwriting -pointInside:withEvent:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// Return NO if even super returns NO (i.e., if point lies outside our bounds)
BOOL superResult = [super pointInside:point withEvent:event];
if (!superResult) {
return superResult;
}
// Don't check again if we just queried the same point
// (because pointInside:withEvent: gets often called multiple times)
if (CGPointEqualToPoint(point, self.previousTouchPoint)) {
return self.previousTouchHitTestResponse;
} else {
self.previousTouchPoint = point;
}
BOOL response = NO;
if (self.buttonImage == nil && self.buttonBackground == nil) {
response = YES;
}
else if (self.buttonImage != nil && self.buttonBackground == nil) {
response = [self isAlphaVisibleAtPoint:point forImage:self.buttonImage];
}
else if (self.buttonImage == nil && self.buttonBackground != nil) {
response = [self isAlphaVisibleAtPoint:point forImage:self.buttonBackground];
}
else {
if ([self isAlphaVisibleAtPoint:point forImage:self.buttonImage]) {
response = YES;
} else {
response = [self isAlphaVisibleAtPoint:point forImage:self.buttonBackground];
}
}
self.previousTouchHitTestResponse = response;
return response;
}
I've successfully made it that my UITableViewCell can be slid right and left to mark the cell as read, or to delete it (sort of like how the Reeder app does it) but now it doesn't allow me to simply tap it. How do I allow it to be tapped as well?
I used this article for the structure of the concept, so more information is available there.
I implemented the sliding as follows:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
_firstTouchPoint = [[touches anyObject] locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint touchPoint = [[touches anyObject] locationInView:self];
// Holds the value of how far away from the first touch the finger has moved
CGFloat xPos;
// If the first touch point is left of the current point, it means the user is moving their finger right and the cell must move right
if (_firstTouchPoint.x < touchPoint.x) {
xPos = touchPoint.x - _firstTouchPoint.x;
if (xPos <= 0) {
xPos = 0;
}
}
else {
xPos = -(_firstTouchPoint.x - touchPoint.x);
if (xPos >= 0) {
xPos = 0;
}
}
// Change our cellFront's origin to the xPos we defined
CGRect frame = self.cellFront.frame;
frame.origin = CGPointMake(xPos, 0);
self.cellFront.frame = frame;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self springBack];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self springBack];
}
- (void)springBack {
CGFloat cellXPositionBeforeSpringBack = self.cellFront.frame.origin.x;
CGRect frame = self.cellFront.frame;
frame.origin = CGPointMake(0, 0);
[UIView animateWithDuration:0.1 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.cellFront.frame = frame;
} completion:^(BOOL finished) {
}];
if (cellXPositionBeforeSpringBack >= 80 || cellXPositionBeforeSpringBack <= -80) {
NSLog(#"YA");
}
}
When I tap it though, nothing happens.
Just use UIPanGestureRecognizer.
the touchesBegan approach is so old.
With the pan Gesture you will not lose the touch event on your cell.
The easiest to solve it, is actually to build your own gesture for sliding right and left.
After you build it, just add this gesture to the view, and add the tap gesture as well.
If you allow the both gestures (tap gesture and slide gesture) to live simultaneously by implementing the 'shouldRecognizeSimultaneouslyWithGestureRecognizer' method, iOS will take care of it for you very easily.
Here how to build custom gesture recogniser:
http://blog.federicomestrone.com/2012/01/31/creating-custom-gesture-recognisers-for-ios/
I just implemented this:
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)panGestureRecognizer {
CGPoint translation = [panGestureRecognizer translationInView:someView];
return fabs(translation.y) > fabs(translation.x);
}
(As outlined here.)
But if the user pans vertically just over the diagonal it will start. How do I make the tolerance much more strict for what it considers vertical?
Basically, the image below describes what I'm after. The first diagram is what it detects now, anything within that area, and the second is what I want it to do.
You can use atan2f given x and y values to calculate the angle from vertical. For example, to start the gesture if the angle is less than 4 degrees from vertical, you can do something like this:
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gesture {
CGPoint translation = [gesture translationInView:gesture.view];
if (translation.x != 0 || translation.y != 0) {
CGFloat angle = atan2f(fabs(translation.x), fabs(translation.y));
return angle < (4.0 * M_PI / 180.0); // four degrees, but in radians
}
return FALSE;
}
Detecting pure vertical gestures, I assume that translation.x == 0 then.
You should as well, check the correct answer from the post you referenced. Where he compares the previous location with the current one. You can create the sensibility. You can check my project, for example to see that, where I use this sensibility to define, when an action is valid (less or equal than the sensibility) or invalid (bigger than the sensibility). Check the MOVEMENT_SENSIBILITY inside the RPSliderViewController.m.
I've written a UIGestureRecognizer subclass for that purpose once. It only tracks the vertical translation. Maybe that helps you. You can use it as any other gesture recognizer, just set the threshold and track the translation in the it's target's action method.
VerticalPanGestureRecognizer.h
#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
#interface VerticalPanGestureRecognizer : UIGestureRecognizer
#property (assign, nonatomic)float translation;
#property (assign, nonatomic)float offsetThreshold;
#end
VerticalPanGestureRecognizer.m
#import "VerticalPanGestureRecognizer.h"
#interface VerticalPanGestureRecognizer ()
{
CGPoint _startPoint;
}
#end
#implementation VerticalPanGestureRecognizer
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([touches count] > 1) {
self.state = UIGestureRecognizerStateFailed;
}
else
{
_startPoint = [[touches anyObject] locationInView:self.view];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.state == UIGestureRecognizerStateFailed || self.state == UIGestureRecognizerStateCancelled) {
return;
}
CGPoint currentLocation = [[touches anyObject] locationInView:self.view];
CGPoint translation;
translation.x = currentLocation.x - _startPoint.x;
translation.y = currentLocation.y - _startPoint.y;
if (self.state == UIGestureRecognizerStatePossible)
{
//if the x-translation is above our threshold the gesture fails
if (fabsf(translation.x) > self.offsetThreshold)
self.state = UIGestureRecognizerStateFailed;
//if the y-translation has reached the threshold the gesture is recognized and the we start sending action methods
else if (fabsf(translation.y) > self.offsetThreshold)
self.state = UIGestureRecognizerStateBegan;
return;
}
//if we reached this point the gesture was succesfully recognized so we now enter changed state
self.state = UIGestureRecognizerStateChanged;
//we are just insterested in the vertical translation
self.translation = translation.y;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//if at this point the state is still 'possible' the threshold wasn't reached at all so we fail
if (self.state == UIGestureRecognizerStatePossible)
{
self.state = UIGestureRecognizerStateFailed;
}
else
{
CGPoint currentLocation = [[touches anyObject] locationInView:self.view];
CGPoint translation;
translation.x = _startPoint.x - currentLocation.x;
translation.y = _startPoint.y - currentLocation.y;
self.translation = translation.y;
self.state = UIGestureRecognizerStateEnded;
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
self.state = UIGestureRecognizerStateCancelled;
}
- (void)reset
{
[super reset];
_startPoint = CGPointZero;
}
#end
I'm trying to use UIPanGestureRecognizer to slide a button between two points (like a volume slider). The following code allows me to slide the button back and forth, but the button doesn't stop at the same point every time (i.e., when I slide it to the right, sometime it stops where it's supposed to, sometimes it stops +/- 10 pixels from where it's supposed to). What am I doing wrong?
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint location = [recognizer locationInView:self];
// 1
if (recognizer.state == UIGestureRecognizerStateBegan) {
// if the gesture just started, record current center location
_originalCenter = self.sliderButton.center;
}
// 2
if (recognizer.state == UIGestureRecognizerStateChanged) {
// move the checkmarks and main label based on touch
//CGPoint translation = [recognizer translationInView:self];
// move slider button
if (location.x < 70 + 178 && location.x > 70) {
self.sliderButton.center = CGPointMake(location.x, _originalCenter.y);
}
// determine whether the item has been dragged far enough to initiate a removal
if (location.x > 170 + 70) {
_draggedToEnd = YES;
} else {
_draggedToEnd = NO;
}
}
// 3
if (recognizer.state == UIGestureRecognizerStateEnded) {
if (_draggedToEnd) {
// notify the delegate that this item should be deleted
[self buttonDraggedToEnd];
}
}
}
I want to detect a two-finger diagonal swipe that starts from the bottom right of the screen to the middle. I tried adding UISwipeGestureRecognizer with direction set as "UISwipeGestureRecognizerDirectionUp | UISwipeGestureRecognizerDirectionLeft" but to no vail as the handler is getting invoked even if I start the swipe from the middle of the screen to the top left.
Do I need to sub-class UIGestureRecognizer or can I handle this using touchesBegan and touchesMoved ?
You need to subclass it to use touchesBegan and touchesMoved anyway. I'd do UISwipeGestureRecognizerDirectionUp | UISwipeGestureRecognizerDirectionLeft like you said, then set up two CGRects, one for acceptable starting points, one for acceptable end points. Then use the UIGestureRecognizerDelegate method gestureRecognizer:shouldReceiveTouch:, and check if the touch point is in the acceptable start rect by using CGRectContainsPoint(). Then (I think) in your UISwipeGestureRecognizer subclass, override touchesEnded:withEvent:, and check if the end touch is in the acceptable rect. If it's not, set the state to UIGestureRecognizerStateCancelled (or however you're supposed to cancel a gesture). Also make sure the view it's attached to has its multipleTouchEnabled property set. I haven't actually tried this, but something of the sort should do it. Good luck!
EDIT
Actually, if you didn't want to have to worry about specific rect values for the acceptable start/end points, and make it device independent, you could do this:
//swap in whatever percentage of the screen's width/height you want in place of ".75f"
//set the origin for acceptable start points
CGFloat startRect_x = self.view.frame.size.width * .75f;
CGFloat startRect_y = self.view.frame.size.height * .75f;
//set the size
CGFloat rectWidth = self.view.frame.size.width - startRect_x;
CGFloat rectHeight = self.view.frame.size.height - startRect_y;
//make the acceptable start point rect
CGRect startRect = CGRectMake(startRect_x, startRect_y, rectWidth, rectHeight);
//set the origin for the accepable end points
CGFloat endRect_x = self.view.center.x - rectWidth/2;
CGFloat endRect_y = self.view.center.y - rectHeight/2;
//make the acceptable end point rect
CGRect endRect = CGRectMake(endRect_x, endRect_y, rectWidth, rectHeight);
the touches method of cause can do every Gesture, the Gesture is supported since ios3.2.
i can give you a simple code , you maybe modify by yourself.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
isTwoFingersBegin = NO;
isTwoFingersEnd = NO;
UITouch *touch = [touches anyObject];
UIView *touchView = [touch view];
if ([touches count] == 2)
{
isTwoFingersBegin = YES;
}
touchStartPoint = [touch locationInView:self.view];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
isSwip = YES;
}
#define TOUCH_MOVE_EFFECT_DIST 30
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
UIView *touchView = [touch view];
touchEndPoint = [touch locationInView:self.view];
if ([touches count] == 2)
{
isTwoFingersEnd = YES;
}
CGPoint deltaVector = CGPointMake(touchEndPoint.x - touchStartPoint.x, touchEndPoint.y - touchStartPoint.y);
if (fabsf(deltaVector.x) > TOUCH_MOVE_EFFECT_DIST
&&fabsf(deltaVector.y) > TOUCH_MOVE_EFFECT_DIST
&& isSwip &&isTwoFingersBegin&&isTwoFingersEnd) {
theSwipGesture=RightUpGesture;
}
else if (fabsf(deltaVector.x) <- TOUCH_MOVE_EFFECT_DIST
&& fabsf(deltaVector.y) <- TOUCH_MOVE_EFFECT_DIST
&& isSwip&&isTwoFingersBegin&&isTwoFingersEnd) {
theSwipGesture=LeftDownGesture;
}
isSwip = NO;
}
You can use a UIPanGestureRecognizer
- (void)viewPanned:(UIPanGestureRecognizer *)panGR
{
CGPoint translation = [panGR translationInView:self];
CGFloat threshold = self.bounds.size.width/2;
// Detect
Direction direction = DirectionUnknown;
if (translation.x > threshold) {
if (translation.y > threshold) {
direction = DirectionBottomRight;
} else if (translation.y < -threshold) {
direction = DirectionTopRight;
} else {
direction = DirectionRight;
}
} else if (translation.x < -threshold) {
if (translation.y > threshold) {
direction = DirectionBottomLeft;
} else if (translation.y < -threshold) {
direction = DirectionTopLeft;
} else {
direction = DirectionLeft;
}
} else {
if (translation.y > threshold) {
direction = DirectionBottom;
} else if (translation.y < -threshold) {
direction = DirectionTop;
}
}
}
One possible solution would be to map off a group of coordinates on the iPhone's screen where the swipe could potentially start, and another where it could potentially end. Then in the touchesBegan: and touchesMoved: methods, you could compare the starting and ending points of the swipe and determine whether it qualified as diagonal.
Thanks Guys!
I ended up writing custom code in touchesBegan, touchesMoved and touchesEnded and ot works like charm.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([touches count] == 2) {
CGPoint nowPoint = [[touches anyObject] locationInView:self];
if( nowPoint.x >= ALLOWED_X)
swipeUp = YES;
}
[super touchesBegan:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
if ([touches count] == 2 && swipeUp) {
CGPoint nowPoint = [[touches anyObject] locationInView:self];
CGPoint prevPoint = [[touches anyObject] previousLocationInView:self];
if( nowPoint.x <= prevPoint.x && nowPoint.y <= prevPoint.y){
}
else {
swipeUp = NO;
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
CGPoint nowPoint = [[touches anyObject] locationInView:self];
if ([touches count] == 2 && swipeUp && nowPoint.x <= DELTA_X && nowPoint.y <= DELTA_Y) {
NSLog(#"Invoke Method");
}
swipeUp = NO;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
swipeUp = NO;
}