Draggable UIButton Snap To a Circle Path - ios

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);
}
}

Related

UIButton - move and scale

I have an UIButton that I've creates programmatically. Actually it should'n be UIButton, I just need to have possibility to mark some area above the image.
So the features I need it - move object and resize it. For this i have 2 methods:
- (void) objMove:(id) sender withEvent:(UIEvent *) event
{
UIControl *control = sender;
UITouch *t = [[event allTouches] anyObject];
CGPoint pPrev = [t previousLocationInView:control];
CGPoint p = [t locationInView:control];
CGPoint center = control.center;
center.x += p.x - pPrev.x;
center.y += p.y - pPrev.y;
control.center = center;
}
- (void)objScale:(UIPinchGestureRecognizer *)recognizer
{
UIView *pinchView = recognizer.view;
CGRect bounds = pinchView.bounds;
CGPoint pinchCenter = [recognizer locationInView:pinchView];
pinchCenter.x -= CGRectGetMidX(bounds);
pinchCenter.y -= CGRectGetMidY(bounds);
CGAffineTransform transform = pinchView.transform;
transform = CGAffineTransformTranslate(transform, pinchCenter.x, pinchCenter.y);
CGFloat scale = recognizer.scale;
transform = CGAffineTransformScale(transform, scale, scale);
transform = CGAffineTransformTranslate(transform, -pinchCenter.x, -pinchCenter.y);
pinchView.transform = transform;
recognizer.scale = 1.0;
}
Scale works ok. Moving looks ok until I change the size of object - when i increase object it become moves slower than finger, and vice versa - if object smaller than original it moves faster than finger. why it works like this?
I think you should get startPoint and startCenter in
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// get startPoint and startCenter here
}
- (void) objMove:(id) sender withEvent:(UIEvent *) event
{
UIControl *control = sender;
UITouch *t = [[event allTouches] anyObject];
CGPoint p = [t locationInView:control];
startCenter.x += p.x - startPoint.x;
startCenter.y += p.y - startPoint.y;
control.center = startCenter;
}
Change your code like this, maybe it works.
Your center is current center, p is current point, pPrev is previous point.
current center adds previous point moved size is wrong.
You should get relative distance, not dynamic distance.

Define custom touch area in custom UIControl object

I am creating a custom UIControl object as detailed here. It is all working well except for the touch area.
I want to find a way to limit the touch area to only part of the control, in the example above I want it to be restricted to the black circumference only rather than the whole control area.
Any idea?
Cheers
You can override UIView's pointInside:withEvent: to reject unwanted touches.
Here's a method that checks if the touch occurred in a ring around the center of the view:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
UITouch *touch = [[event touchesForView:self] anyObject];
if (touch == nil)
return NO;
CGPoint touchPoint = [touch locationInView:self];
CGRect bounds = self.bounds;
CGPoint center = { CGRectGetMidX(bounds), CGRectGetMidY(bounds) };
CGVector delta = { touchPoint.x - center.x, touchPoint.y - center.y };
CGFloat squareDistance = delta.dx * delta.dx + delta.dy * delta.dy;
CGFloat outerRadius = bounds.size.width * 0.5;
if (squareDistance > outerRadius * outerRadius)
return NO;
CGFloat innerRadius = outerRadius * 0.5;
if (squareDistance < innerRadius * innerRadius)
return NO;
return YES;
}
To detect other hits on more complex shapes you can use a CGPath to describe the shape and test using CGPathContainsPoint. Another way is to use an image of the control and test the pixel's alpha value.
All that depends on how you build your control.

How can I increase the drag speed of button?

I am making a a dragging code in which I am dragging button, now the problem is when I start dragging it slowly it works cool, but when I started dragging it bit fast its not working good cause button dropped during drag.
How can I increase the speed of drag?
- (void)wasDragged:(UIButton*)button withEvent:(UIEvent*)event{
CGPoint previousLocation;
CGPoint location;
UITouch *touch = [[event touchesForView:button] anyObject];
previousLocation = [touch previousLocationInView:button];
location = [touch locationInView:button];
CGFloat delta_x = location.x - previousLocation.x;
CGFloat delta_y = location.y - previousLocation.y;
button.center = CGPointMake(button.center.x + delta_x,button.center.y + delta_y);
}
simply you can just adjust the delta
CGFloat delta_x = location.x - previousLocation.x;
CGFloat delta_y = location.y - previousLocation.y;
delta_x *= 1.5; //the factor of the speed
delta_y *= 1.5;
button.center = CGPointMake(button.center.x + delta_x,button.center.y + delta_y);
why you don't you just set button.center coordonates as location.x and location.y? beacause location.x and location.y are the coordonates of where your finger is, and there should be the center of button

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).

touchesMoved:withEvent: and UIRotationgestureRecognizer don't work together

I'm trying these two methods to move and rotate an UIView. Both methods work separately but if I rotate and then move the UIView it disappears.
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGRect rect = self.aView.frame;
UITouch *touch = [touches anyObject];
CGPoint pPoint = [touch previousLocationInView:self.view];
CGPoint cPoint = [touch locationInView:self.view];
float deltaX = cPoint.x - pPoint.x;
float deltaY = cPoint.y - pPoint.y;
rect.origin.x = rect.origin.x + deltaX;
rect.origin.y = rect.origin.y + deltaY;
self.aView.frame = rect;
}
- (void)rotate:(UIRotationGestureRecognizer *) recognizer {
CGFloat rotation = angle + recognizer.rotation;
NSLog(#"%f", angle * 180 / M_PI);
self.aView.transform = CGAffineTransformMakeRotation (rotation);
if (recognizer.state == UIGestureRecognizerStateEnded)
angle = rotation;
}
Gesture recognisers take priority over touchMoved, so it's hard to use them both with the same view.
Use a UIPanGestureRecognizer instead of touchMoved to handle dragging the UIView. You can then get the UIPanGestureRecognizer and UIRotationGestureRecognizer to cooperate with one another by implementing the
– gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
method, which is defined in the UIGestureRecognizerDelegate protocol.

Resources