Pan resistance when dragging UIImageView? - ios

some applications have this "drag resistance" when you drag an image. The further away you drag the image from the origin point, the less it becomes drag-able.
How can I implement this feature?

How about subclassing UIImageView and doing something like below: (Not perfect , but something along this lines maybe?)
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint currentPosition = [[touches anyObject] locationInView:self.superview];
CGFloat dx = currentPosition.x - initialCentre.x;//delta x with sign
CGFloat dy = currentPosition.y - initialCentre.y;//delta y with sign
CGFloat distance = sqrt(dx*dx + dy*dy);//distance
CGFloat weightedX = initialCentre.x + dx * (1/log(distance)); //new x as inverse function of distance
CGFloat weightedY = initialCentre.y + dy * (1/log(distance)); //new y as inverse function of distance
self.center = CGPointMake(weightedX, weightedY);//set the center
}

Related

UITapGesture to drag view but limiting the bounds of which it can be dragged

I currently have 2 circles. One big circle and one little circle. The little circle has a tap gesture recognizer that allows it to be dragged by the user. I would like the little circle's center to go no further than the big circle's radius. I have 4 auto layout constraints on the inner circle. 1 for fixed width, 1 for fixed height, 1 for distance from center for x, and 1 for distance from center for y. Here is how I am going about this:
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:self.view];
CGFloat x = recognizer.view.center.x + translation.x;
CGFloat y = recognizer.view.center.y + translation.y;
CGPoint desiredPoint = CGPointMake(x, y);
//check if point the user is trying to get to is outside the radius of the outer circle
//if it is, set the center of the inner circle to the right position at the distance of the radius and with the same angle
if ([self distanceBetweenStartPoint:self.outerCircleView.center endPoint:desiredPoint] > self.outerCircleRadius) {
CGFloat angle = [self angleBetweenStartPoint:self.outerCircleView.center endPoint:actualPosition];
desiredPoint = [self findPointFromRadius:self.outerCircleRadius startPoint:self.outerCircleView.center angle:angle];
}
//adjust the constraints to move the inner circle
self.innerCircleCenterXConstraint.constant += actualPosition.x - recognizer.view.center.x;
self.innerCircleCenterYConstraint.constant += actualPosition.y - recognizer.view.center.y;
[recognizer setTranslation:CGPointMake(0.0, 0.0) inView:self.view];
}
}
- (CGFloat)distanceBetweenStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
CGFloat xDif = endPoint.x - startPoint.x;
CGFloat yDif = endPoint.y - startPoint.y;
//pythagorean theorem
return sqrt((xDif * xDif) + (yDif * yDif));
}
- (CGPoint)findPointFromRadius:(CGFloat)radius startPoint:(CGPoint)startPoint angle:(CGFloat)angle {
CGFloat x = radius * cos(angle) + startPoint.x;
CGFloat y = radius * sin(angle) + startPoint.y;
return CGPointMake(x, y);
}
- (CGFloat)angleBetweenStartPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
CGPoint originPoint = CGPointMake(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
return atan2f(originPoint.y, originPoint.x);
}
This works almost perfectly. The problem is I try to find the percentage that the user moved towards the outside of the circle. So I use the distanceBetweenStartPoint(center of outer circle) endPoint(center of inner circle) method and divide that by the radius of the outer circle. This should give me a value of 1 when the circle has been dragged as far to one side as it can go. Unfortunately I am getting values like 0.9994324 or 1.000923. What could be causing this? Thanks for any insight!

draw a dial like line pointing at user's touch point

Assuming I want to draw a line which resemble a clock dial (blue line), starting from the center of the screen (center) and ending at the user's touch position (A,B or C)
it does not matter how far the finger is, the dial will always have the same length size.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:touch.view];
NSLog(#"Center point = %f %f",self.view.center.x,self.view.center.y);
NSLog(#"finter at point = %f %f",touchLocation.x,touchLocation.y);
// line re drawing itself ...
NSLog(#"end point = %f %f",?,?);
}
You need to know the length of your line. It's unrelated to the touch point correct?
First find the coordinates of the touch relative to the center point
x = Touch.x - center.x
y = Touch.y - center.y
Now we need to get the angle
angle = arctan(y / x)
If the x is negative, adjust by 180 degrees (pi) - This restores what is lost in the division.
Now multiply sin(angle) and cos(angle) by your desired length to get the new point
newX = cos(angle) * length
newY = sin(angle) * length
Here is some Swift code that gets you mostly there. Try it in a playground to verify different touch values and lengths.
let lineLength = 13.0
// Touch points
let x = -5.0
let y = -5.0
// Calculate angle
var angle = atan(y / x)
if x < 0 {
angle += 3.14159;
}
// Get new X and Y
var newX = cos(angle) * lineLength
var newY = sin(angle) * lineLength

CGAffineTransformRotate : Detect rotation direction

What I am trying to do :
I have an Image of a wheel and a scroll view. User can drag the wheel to rotate it in either direction.
By detecting the direction of rotation. I have to scroll the images placed over scroll view.
What I am doing is :
Following this tutorial
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchPoint = [touch locationInView:self];
startTouch = touchPoint; // StartTouch is static varialbel
float dx = touchPoint.x - container.center.x;
float dy = touchPoint.y - container.center.y;
deltaAngle = atan2(dy,dx);
startTransform = container.transform;
return YES;
}
- (BOOL)continueTrackingWithTouch:(UITouch*)touch withEvent:(UIEvent*)event
{
CGPoint pt = [touch locationInView:self];
float dx = pt.x - container.center.x;
float dy = pt.y - container.center.y;
float ang = atan2(dy,dx);
float angleDifference = deltaAngle - ang;
container.transform = CGAffineTransformRotate(startTransform, -angleDifference);
return YES;
}
Main Problem
I am not able to detect the Rotation Direction correctly. I am using the angleDifference varibale from method continueTrackingWithTouch to detect it.
if(angleDifference > 0){
// Positive value move in right direction.
}
else{
// Negative value move in left direction.
}
This is working ok for small drags of less than 360 dgrees, but after that it's not working properly.
Can anyone suggest me correct approach of Detecting the rotation Direction.
Thanks

Draggable UIButton Snap To a Circle Path

Hi I am looking to make my UIButton be able to be draggable only around a circle I created through a draw rect. I may need to make a circle path instead of the circle shape however I'm not quite sure about the approach to take as I am new to this. Here is the code I have and I only want the button to be dragged around that circle or invisible path. Any help or examples would be extremely helpful. Thank You!
Edit: I used part of the answer below below but I am not sure how to get this to work correctly. I want it to be the same size as my circle that I created so I am happy to create just a circle path and put it on top of the circle that I made.
//Button Code
timeSetButton = [UIButton buttonWithType:UIButtonTypeCustom];
[timeSetButton addTarget:self
action:nil
forControlEvents:UIControlEventTouchDown];
[timeSetButton setImage:[UIImage imageNamed:#"testButton.png"] forState: UIControlStateNormal];
timeSetButton.frame = CGRectMake(0,415,50,50);
[self.view addSubview:timeSetButton];
//Button Listener
[timeSetButton addTarget:self action:#selector(wasDragged:withEvent:)
forControlEvents:UIControlEventTouchDragInside];
//Draggable Button Code
// get the touch
UITouch *touch = [[event touchesForView:timeSetButton] anyObject];
// get delta
CGPoint previousLocation = [touch previousLocationInView:timeSetButton];
CGPoint location = [touch locationInView:timeSetButton];
CGFloat delta_x = location.x - previousLocation.x;
CGFloat delta_y = location.y - previousLocation.y;
// move button
timeSetButton.center = CGPointMake(timeSetButton.center.x + delta_x,
timeSetButton.center.y + delta_y);
// enforce constraint on locations, by...
// working out the distance from the centre of the circle
CGPoint vectorFromCentreOfCircle =
CGPointMake(150,150);
CGFloat distanceFromCentreOfCircle = hypotf(vectorFromCentreOfCircle.x, vectorFromCentreOfCircle.y);
// working out what you'd need to multiply that distance by in order
// to get the specified radius
CGFloat correctionMultiplier = 20 / distanceFromCentreOfCircle;
// adjust vector from centre of circle
vectorFromCentreOfCircle.x *= correctionMultiplier;
vectorFromCentreOfCircle.y *= correctionMultiplier;
// move button one more time
timeSetButton.center = CGPointMake(
200 + vectorFromCentreOfCircle.x,
200 + vectorFromCentreOfCircle.y);
Here is the Circle Shape
circleView = [[CircleView alloc] initWithFrame:CGRectMake(55, 100, 260, 260)];
circleView.backgroundColor = [UIColor clearColor];
[self.view addSubview:circleView];
[self.view sendSubviewToBack:circleView];
I actually needed the same and was watching your code. What you actually need to do is fetch the angle of the actual circle and then calculate the needed x and y on that. This is how I have done it:
UIButton *button = (UIButton *) sender;
UITouch *touch = [[event touchesForView:button] anyObject];
//Drawing the circle
CGPoint arcCenter = CGPointMake(385.0f, 700.0f);
CGFloat arcRadius = 140.0f;
// get delta
CGPoint previousLocation = [touch previousLocationInView:button];
CGPoint location = [touch locationInView:button];
CGFloat delta_x = location.x - previousLocation.x;
CGFloat delta_y = location.y - previousLocation.y;
// move button
button.center = CGPointMake(button.center.x + delta_x, button.center.y + delta_y);
CGFloat angle = atan2((button.center.y - arcCenter.y), (button.center.x - arcCenter.x));
button.center = CGPointMake(arcCenter.x + arcRadius * cos(angle), arcCenter.y + arcRadius * sin(angle));
This gives me when I add a drag to a button the actual needed behavior. I used Touch Drag Outside and Inside to avoid the button to stop when you move your finger too much away from the button. I hope it will help you too.
For this sort of thing it's probably sufficient just to add a quick bit of code after you've moved the button to enforce the constraint that it must be on the circle. E.g.
// move button
timeSetButton.center = CGPointMake(button.center.x + delta_x,
button.center.y + delta_y);
// enforce constraint on locations, by...
// working out the distance from the centre of the circle
CGPoint vectorFromCentreOfCircle =
CGPointMake(timeSetButton.center.x - centreOfCircle.x,
timeSetButton.center.x - centreOfCircle.y);
CGFloat distanceFromCentreOfCircle =
hypotf(vectorFromCentreOfCircle.x, vectorFromCentreOfCircle.y);
// working out what you'd need to multiply that distance by in order
// to get the specified radius
CGFloat correctionMultiplier = radiusOfCircle / distanceFromCentreOfCircle;
// adjust vector from centre of circle
vectorFromCentreOfCircle.x *= correctionMultiplier;
vectorFromCentreOfCircle.y *= correctionMultiplier;
// move button one more time
timeSetButton.center = CGPointMake(
centreOfCircle.x + vectorFromCentreOfCircle.x,
centreOfCircle.y + vectorFromCentreOfCircle.y);
You can do this simply by knowing the distance of touchPoint from the center of the view
I'll make the following modification in your code. See if this works
- (void)wasDragged:(UIButton *)button withEvent:(UIEvent *)event
{
// get the touch
UITouch *touch = [[event touchesForView:button] anyObject];
// get delta
CGPoint previousLocation = [touch previousLocationInView:button];
CGPoint location = [touch locationInView:button];
//calculate center of button in superView
CGPoint buttonCenter = CGPointMake(button.frame.origin.x+(button.frame.size.width/2.0f),
button.frame.origin.y+(button.frame.size.height/2.0f));
//calculate the distance of current touchPoint from buttonCenter
CGFloat diffx = location.x - buttonCenter.x;
CGFloat diffy = location.y - buttonCenter.y;
CGFloat distance = sqrtf((diffx*diffx)+(diffy*diffy));
//check if the distance is within the radius of circle.
//assuming that your button is always a square to make
//perfect circle within it.
CGFloat radius = button.frame.size.width/2.0f;
if(radius >= distance)//this makes a circular check.
{
CGFloat delta_x = location.x - previousLocation.x;
CGFloat delta_y = location.y - previousLocation.y;
// move button
timeSetButton.center = CGPointMake(button.center.x + delta_x,
button.center.y + delta_y);
}
}

UIImageView rotation animation in the touched direction

I have one UIImageView having an image of an arrow. When user taps on the UIView this arrow should point to the direction of the tap maintaing its position it should just change the transform. I have implemented following code. But it not working as expected. I have added a screenshot. In this screenshot when i touch the point upper left the arrow direction should be as shown.But it is not happening so.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[[event allTouches]anyObject];
touchedPoint= [touch locationInView:touch.view];
imageViews.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(rangle11));
previousTouchedPoint = touchedPoint ;
}
- (CGFloat) pointPairToBearingDegrees:(CGPoint)startingPoint secondPoint:(CGPoint) endingPoint
{
CGPoint originPoint = CGPointMake(endingPoint.x - startingPoint.x, endingPoint.y - startingPoint.y); // get origin point to origin by subtracting end from start
float bearingRadians = atan2f(originPoint.y, originPoint.x); // get bearing in radians
float bearingDegrees = bearingRadians * (180.0 / M_PI); // convert to degrees
bearingDegrees = (bearingDegrees > 0.0 ? bearingDegrees : (360.0 + bearingDegrees)); // correct discontinuity
return bearingDegrees;
}
I assume you wanted an arrow image to point to where ever you touch, I tried and this is what i could come up with. I put an image view with an arrow pointing upwards (haven't tried starting from any other position, log gives correct angles) and on touching on different locations it rotates and points to touched location. Hope it helps ( tried some old math :-) )
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[[event allTouches]anyObject];
touchedPoint= [touch locationInView:touch.view];
CGFloat angle = [self getAngle:touchedPoint];
imageView.transform = CGAffineTransformMakeRotation(angle);
}
-(CGFloat) getAngle: (CGPoint) touchedPoints
{
CGFloat x1 = imageView.center.x;
CGFloat y1 = imageView.center.y;
CGFloat x2 = touchedPoints.x;
CGFloat y2 = touchedPoints.y;
CGFloat x3 = x1;
CGFloat y3 = y2;
CGFloat oppSide = sqrtf(((x2-x3)*(x2-x3)) + ((y2-y3)*(y2-y3)));
CGFloat adjSide = sqrtf(((x1-x3)*(x1-x3)) + ((y1-y3)*(y1-y3)));
CGFloat angle = atanf(oppSide/adjSide);
// Quadrant Identifiaction
if(x2 < imageView.center.x)
{
angle = 0-angle;
}
if(y2 > imageView.center.y)
{
angle = M_PI/2 + (M_PI/2 -angle);
}
NSLog(#"Angle is %2f",angle*180/M_PI);
return angle;
}
-anoop4real
Given what you told me, I think the problem is that you are not resetting your transform in touchesBegan. Try changing it to something like this and see if it works better:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[[event allTouches]anyObject];
touchedPoint= [touch locationInView:touch.view];
imageViews.transform = CGAffineTransformIdentity;
imageViews.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(rangle11));
previousTouchedPoint = touchedPoint ;
}
Do you need the line to "remove the discontinuity"? Seems atan2f() returns values between +π to -π. Won't those work directly with CATransform3DMakeRotation()?
What you need is that the arrow points to the last tapped point. To simplify and test, I have used a tap gesture (but it's similar to a touchBegan:withEvent:).
In the viewDidLoad method, I register the gesture :
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
[self.view addGestureRecognizer:tapGesture];
[tapGesture release];
The method called on each tap :
- (void)tapped:(UITapGestureRecognizer *)gesture
{
CGPoint imageCenter = mFlecheImageView.center;
CGPoint tapPoint = [gesture locationInView:self.view];
double deltaY = tapPoint.y - imageCenter.y;
double deltaX = tapPoint.x - imageCenter.x;
double angleInRadians = atan2(deltaY, deltaX) + M_PI_2;
mFlecheImageView.transform = CGAffineTransformMakeRotation(angleInRadians);
}
One key is the + M_PI_2 because UIKit coordinates have the origin at the top left corner (while in trigonometric, we use a bottom left corner).

Resources