Detect closer point inside a view from touch - objective-c

There's a way to, when the user touches outside the view, the app detects the closer point inside this view? I want to detect just like the image below.
EDIT:
CGPoint touchPoint = [[touches anyObject] locationInView:self.view];
if (CGRectContainsPoint([_grayView frame], touchPoint)) {
// The touch was inside the gray view
} else {
// The touch was outside the view. Detects where's the closer CGPoint inside the gray view.
// The detection must be related to the whole view (In the image example, the CGPoint returned would be related to the whole screen)
}

static float bound(float pt, float min, float max)
{
if(pt < min) return min;
if(pt > max) return max;
return pt;
}
static CGPoint boundPoint(CGPoint touch, CGRect bounds)
{
touch.x = bound(touch.x, bounds.origin.x, bounds.origin.x + bounds.size.width;
touch.y = bound(touch.y, bounds.origin.y, bounds.origin.y + bounds.size.height;
return touch;
}

All you need is a little math:
Ask the touch for its locationInView: with the view in question as the argument.
Compare the point’s x with the view’s bounds, clamping to the extrema of that CGRect.
There is no step three, the result of the above is the point you are looking for.

Try this code!
CGPoint pt = [touches locationInView:childView];
if(pt.x >= 0 && pt.x <= childView.frame.size.width
&& pt.y >= 0 && pt.y <= childView.frame.size.height) {
NSLog(#"Touch inside rect");
return;
}
pt.x = MIN(childView.frame.size.width, MAX(0, pt.x));
pt.y = MIN(childView.frame.size.height, MAX(0, pt.y));
// and here is the point
NSLog(#"The closest point is %f, %f", pt.x, pt.y);

Related

Scaling view depending on x coordinate while being dragged

I have been trying to implement a UI feature which I've seen in a few apps which use cards to display information. My current view controller looks like this:
and users are able to drag the card along the x axis to the left and right. Dragging to the right side of the screen does nothing to the scale of the card (simply changes position) but if the user swipes it to the left I wanted to slowly decrease the scale of it depending on its y coordinate (e.g. the scale is smallest when card is furthest to the left, getting bigger from that point until the original size is reached). If the card is dragged far enough to the left it will fade out, but if the user does not drag it far enough it increases in scale and moves back into the middle. Code I've tried so far:
- (void)handlePanImage:(UIPanGestureRecognizer *)sender
{
static CGPoint originalCenter;
if (sender.state == UIGestureRecognizerStateBegan)
{
originalCenter = sender.view.center;
sender.view.alpha = 0.8;
[sender.view.superview bringSubviewToFront:sender.view];
}
else if (sender.state == UIGestureRecognizerStateChanged)
{
CGPoint translation = [sender translationInView:self.view];
NSLog(#"%f x %f y",translation.x ,translation.y);
sender.view.center=CGPointMake(originalCenter.x + translation.x, yOfView);
CGAffineTransform transform = sender.view.transform;
i-=0.001;
transform = CGAffineTransformScale(transform, i, i);
//transform = CGAffineTransformRotate(transform, self.rotationAngle);
sender.view.transform = transform;
}
else if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateCancelled || sender.state == UIGestureRecognizerStateFailed)
{
if(sender.view.center.x>0){
[UIView animateWithDuration:0.2 animations:^{
CGRect rect=[sender.view frame];
rect.origin.x=([self.view frame].size.width/2)-_protoypeView.frame.size.width/2;
rect.size.height=originalHeight;
rect.size.width=originalWidth;
[sender.view setFrame:rect];
i=1.0;
}];
}
[UIView animateWithDuration:0.1 animations:^{
sender.view.alpha = 1.0;
}];
}
}
This seems very buggy and doesn't properly work. I also tried to change scale according to translation:
else if (sender.state == UIGestureRecognizerStateChanged)
{
CGPoint translation = [sender translationInView:self.view];
NSLog(#"%f x %f y",translation.x ,translation.y);
sender.view.center=CGPointMake(originalCenter.x + translation.x, yOfView);
CGAffineTransform transform = sender.view.transform;
transform = CGAffineTransformScale(transform, translation.x/100, translation.x/100);
//transform = CGAffineTransformRotate(transform, self.rotationAngle);
sender.view.transform = transform;
}
but the scale either gets too big or too small. Any help would be greatly appreciated :)
In the last piece of code you tried, you're doing a couple of things wrong. The scale transform will be cumulative because you never set the translation of your pan gesture recognizer back to 0. Also, if you look at the math in your transform, you'll see that a small movement of say -1 point would scale the view to 0.01 times its original size. You obviously don't want that. You need to add that small negative number to 1, so that initial -1 point move would scale to 0.99. The change of setting the panner's translation back to zero necessitates changing the center calculation to use sender.view.center.x rather than originalCenter.X. You also need an if statement to check whether the center is left or right of its starting position so you know whether you should apply the scaling transform. Something like this,
-(void)handlePan:(UIPanGestureRecognizer *) sender {
if (sender.state == UIGestureRecognizerStateBegan) {
originalCenter = sender.view.center;
sender.view.alpha = 0.8;
[sender.view.superview bringSubviewToFront:sender.view];
}else if (sender.state == UIGestureRecognizerStateChanged){
CGPoint translation = [sender translationInView:self.view];
sender.view.center=CGPointMake(sender.view.center.x + translation.x, sender.view.center.y);
if (sender.view.center.x < originalCenter.x) {
CGAffineTransform transform = sender.view.transform;
transform = CGAffineTransformScale(transform, 1+translation.x/100.0, 1+translation.x/100.0);
sender.view.transform = transform;
}
[sender setTranslation:CGPointZero inView:self.view];
}
}
This doesn't take care of animating the view back, or fading out the view, but it should get you most of the way there.

More Precise CGPoint for UILongPressGestureRecognizer

I am using a UILongPressGestureRecognizer which works perfectly but the data I get is not precise enough for my use case. CGPoints that I get are rounded off I think.
Example points that I get: 100.5, 103.0 etc. The decimal part is either .5 or .0 . Is there a way to get more precise points? I was hoping for something like .xxxx as in '100.8745' but .xx would do to.
The reason I need this is because I have a circular UIBezierPath, I want to restrict a drag gesture to only that circular path. The item should only be draggable along the circumference of this circle. To do this I calculated 720 points on the circle's boundary using it's radius. Now these points are .xxxx numbers. If I round them off, the drag is not as smooth around the middle section of the circle.This is because in the middle section, the equator, the points on the x-coordinate are very close together. So when I rounded of the y-coordinate, I lost a lot of points and hence the "not so smooth" drag action.
Here is how I calculate the points
for (CGFloat i = -154;i<154;i++) {
CGPoint point = [self pointAroundCircumferenceFromCenter:center forX:i];
[bezierPoints addObject:[NSValue valueWithCGPoint:point]];
i = i - .5;
}
- (CGPoint)pointAroundCircumferenceFromCenter:(CGPoint)center forX:(CGFloat)x
{
CGFloat radius = 154;
CGPoint upperPoint = CGPointZero;
CGPoint lowerPoint = CGPointZero;
//theta used to be the x variable. was first calculating points using the angle
/* point.x = center.x + radius * cosf(theta);
point.y = center.y + radius * sinf(theta);*/
CGFloat y = (radius*radius) - (theta*theta);
upperPoint.x = x+156;
upperPoint.y = 230-sqrtf(y);
lowerPoint.x = x+156;
lowerPoint.y = sqrtf(y)+230;
NSLog(#"x = %f, y = %f",upperPoint.x, upperPoint.y);
[lowerPoints addObject:[NSValue valueWithCGPoint:lowerPoint]];
[upperPoints addObject:[NSValue valueWithCGPoint:upperPoint]];
return upperPoint;
}
I know the code is weird I mean why would I add the points into arrays and return one point back.
Here is how I restrict the movement
-(void)handleLongPress:(UILongPressGestureRecognizer *)recognizer{
CGPoint finalpoint;
CGPoint initialpoint;
CGFloat y;
CGFloat x;
CGPoint tempPoint;
if(recognizer.state == UIGestureRecognizerStateBegan){
initialpoint = [recognizer locationInView:self.view];
CGRect rect = CGRectMake(initialpoint.x, initialpoint.y, 40, 40);
self.hourHand.frame = rect;
self.hourHand.center = initialpoint;
NSLog(#"Long Press Activated at %f,%f",initialpoint.x, initialpoint.y );
}
else if (recognizer.state == UIGestureRecognizerStateChanged){
CGPoint currentPoint = [recognizer locationInView:self.view];
x = currentPoint.x-initialpoint.x;
y = currentPoint.y-initialpoint.y;
tempPoint = CGPointMake( currentPoint.x, currentPoint.y);
NSLog(#"temp point ::%f, %f", tempPoint.x, tempPoint.y);
tempPoint = [self givePointOnCircleForPoint:tempPoint];
self.hourHand.center = tempPoint;
}
else if (recognizer.state == UIGestureRecognizerStateEnded){
// finalpoint = [recognizer locationInView:self.view];
CGRect rect = CGRectMake(tempPoint.x, tempPoint.y, 20, 20);
self.hourHand.frame = rect;
self.hourHand.center = tempPoint;
NSLog(#"Long Press DeActivated at %f,%f",tempPoint.x, tempPoint.y );
}
}
-(CGPoint)givePointOnCircleForPoint:(CGPoint) point{
CGPoint resultingPoint;
for (NSValue *pointValue in allPoints){
CGPoint pointFromArray = [pointValue CGPointValue];
if (point.x == pointFromArray.x) {
// if(point.y > 230.0){
resultingPoint = pointFromArray;
break;
// }
}
}
Basically, I taking the x-coordinate of the "touched point" and returning the y by comparing it to the array of points I calculated earlier.
Currently this code works for half a circle only because, each x has 2 y values because it's a circle, Ignore this because I think this can be easily dealt with.
In the picture, the white circle is the original circle, the black circle is the circle of the points I have from the code+formatting it to remove precision to fit the input I get. If you look around the equator(red highlighted part) you will see a gap between the next points. This gap is my problem.
To answer your original question: On a device with a Retina display, one pixel is 0.5 points, so 0.5 is the best resolution you can get on this hardware.
(On non-Retina devices, 1 pixel == 1 point.)
But it seems to me that you don't need that points array at all. If understand the problem correctly, you can use the following code to
"restrict" (or "project") an arbitrary point to the circumference of the circle:
CGPoint center = ...; // Center of the circle
CGFloat radius = ...; // Radius of the circle
CGPoint point = ...; // The touched point
CGPoint resultingPoint; // Resulting point on the circumference
// Distance from center to point:
CGFloat dist = hypot(point.x - center.x, point.y - center.y);
if (dist == 0) {
// The touched point is the circle center.
// Choose any point on the circumference:
resultingPoint = CGPointMake(center.x + radius, center.y);
} else {
// Project point to circle circumference:
resultingPoint = CGPointMake(center.x + (point.x - center.x)*radius/dist,
center.y + (point.y - center.y)*radius/dist);
}

Rotate a 3D Object using Touch inputs in iOS

I am working on an ios application where I need to rotate a 3d matrix (3D object) just by swiping a finger over it. the method i use for the rotation is
setRotationMatrix(float angle, float x, float y, float z, float *matrix);
However I am not sure how to use the touch's x and y location values in order to rotate the 3D matrix correctly. Here is the code that I am using
-(IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
if(recognizer.state == UIGestureRecognizerStateBegan)
{
panStartPoint = [recognizer locationInView:self];
previousPoint.x = 0;
previousPoint.y = 0;
currentPoint.x = panStartPoint.x;
currentPoint.y = panStartPoint.y;
}
else if(recognizer.state == UIGestureRecognizerStateEnded)
{
panEndPoint = [recognizer locationInView:self];
}
else if (recognizer.state == UIGestureRecognizerStateChanged)
{
previousPoint.x = currentPoint.x;
previousPoint.y = currentPoint.y;
CGPoint instanteneousPoint = [recognizer locationInView:self];
currentPoint.x = instanteneousPoint.x;
currentPoint.y = instanteneousPoint.y;
int xdifference = currentPoint.x - previousPoint.x;
int ydifference = currentPoint.y - previousPoint.y;
if((xdifference <= rotationThreshold *-1) || xdifference >= rotationThreshold) //rotationThreshold = 3;
{
if(xdifference <= rotationThreshold *-1)
{
rotationY = -1.0;
}
else
{
rotationY = 1.0;
}
if( (ydifference >= rotationThreshold *-1) && ydifference <= rotationThreshold )
{
rotationX = 0.0;
}
}
if((ydifference <= rotationThreshold *-1) || ydifference >= rotationThreshold)
{
if((ydifference <= rotationThreshold *-1))
{
rotationX = -1.0;
}
else
{
rotationX = 1.0;
}
if( (xdifference >= rotationThreshold *-1) && xdifference <= rotationThreshold )
{
rotationY = 0.0;
}
}
if(rotationX != 0.0 || rotationY != 0.0)
{
rotationDegree += 5.0f;
if(rotationDegree >= 360.0f)
{
rotationDegree = 0.0f;
}
ShaderUtils::rotatePoseMatrix(rotationDegree, rotationX, rotationY, rotationZ, &modelViewMatrix.data[0]); //rotationZ = 0;
}}}
This is providing me with some rotation of the 3D object but its not as natural as it should be. Any help will be much appreciated. Thanks.
well..i never used used UIPanGestureRecognizer so i dont know how and why doesnt it work but i used UITouch to move a 3d object (i find it to be easyer)
ok..so here is what i did (i dont have the code anymore so i cant give it to you ...sorry):
you get the first touch with touchesBegan (UITouchPhaseBegan)
then, if touchesMoved you rotate the object over x axis if touch moved on y and rotate it on y axis if touch moved on x ..and any combination of the 2 (you have to set sensitivity yourself)
also you have the option to move the object in view like this: you tap twice and then move the finger (like a laptop's track pad)
to move it on the z axis just use UIPinchGestureRecognizer
its obvious you can't move a 3d object in all 3 dimensions with a 2d surface
for more look in UITouch class reference
i may not have explained very well...but you get the basic idea
I think this should help you. It is tutorial with several ways to make it in the right way

Dragging a UITableView

I am using a UIPanGestureRecognizer to allow my UITableView to be dragged around. I have currently set it so that the UITableView cannot be dragged past 0 or half its width. But now, its frame is set to 0 when I try and drag the UITableView back to 0 from an origin of greater than 0. How can I prevent this and allow dragging of the UITableView back to 0? I have tried the following, but I can't quite find out why the outlined code is causing this.
- (void) handlePan:(UIPanGestureRecognizer *) pan {
CGPoint point = [pan translationInView:_tableView];
CGRect frame = [_tableView frame];
if (point.x <= _tableView.frame.size.width / 2) {
frame.origin.x = point.x;
}
NSLog(#"%f : %f", frame.origin.x, _tableView.frame.origin.x);
//outline begin!
if (frame.origin.x < 0 && _tableView.frame.origin.x >= 0) {
frame.origin.x = 0;
}
//outline end!
isFilterViewShowing = frame.origin.x > 0;
[_tableView setFrame:frame];
}
This is not the prettiest code, but that is working in the simulator.
For this code to work you need to add an instance variable.
This code may not behave exactly as you want it, because it's keeping track of "negative" x position, so you get some "threshold" effect that you may not want depending on you design choice.
- (void) handlePan:(UIPanGestureRecognizer *) pan {
if (pan.state == UIGestureRecognizerStateBegan)
{
// cache the starting point of your tableView in an instance variable
xStarter = _tableView.frame.origin.x;
}
// What is the translation
CGPoint translation = [pan translationInView:self.tableView];
// Where does it get us
CGFloat newX = xStarter + translation.x;
CGFloat xLimit = self.tableView.superview.bounds.size.width / 2;
if (newX >= 0.0f && newX <= xLimit)
{
// newX is good, don't touch it
}
else if (newX < 0)
{
newX = 0;
}
else if (newX > xLimit)
{
newX = xLimit;
}
CGRect frame = self.tableView.frame;
frame.origin.x = newX;
[_tableView setFrame:frame];
if (pan.state == UIGestureRecognizerStateEnded)
{
// reset your starter cache
xStarter = 0;
}
}
Have you notice how [pan translationInView:aView]; is returning the offset of the pan gesture and not the position of the finger on the screen.
That is why your code don't work as you expect.

Trying to rotate my sprite smoothly at a constant speed. But it decreases the speed

(I'm using cocos2d for iphone)
First I'll explain you my code: (the important parts)
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
float dstAngle = CC_RADIANS_TO_DEGREES(-ccpToAngle(ccpSub(location, plane.position)));
plane.dstAngle = dstAngle;
}
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
float dstAngle = CC_RADIANS_TO_DEGREES(-ccpToAngle(ccpSub(location, plane.position)));
plane.dstAngle = dstAngle; }
If I touch the screen I calculate the angle between my plane and the touch location.
Now I set the "destination" angle of the plane.
My "plane" is a subclass.
In my header file I have these float values:
//this is in my plane subclass
float diffAngle_;
float startAngle_;
float dstAngle_;
float smoothRotationSpeed_;
EDIT: In my init of the layer I set the "smoothRotationSpeed" to a value. The "rotationTick" method IS scheduled.
I have a tick method to rotate the plane (also in the plane subclass)
-(void)rotateTick:(ccTime)dt {
startAngle_ = [self rotation];
if (startAngle_ > 0)
startAngle_ = fmodf(startAngle_, 360.0f);
else
startAngle_ = fmodf(startAngle_, -360.0f);
diffAngle_ =dstAngle_ - startAngle_;
if (diffAngle_ > 180)
diffAngle_ -= 360;
if (diffAngle_ < -180)
diffAngle_ += 360;
[self setRotation: startAngle_ + diffAngle_ * dt]; //smooth rotate
}
-(void)setSmoothRotationSpeed:(float)smoothRotationSpeed {
[self schedule:#selector(rotateTick:)]; //this will be called if the "smoothRotationSpeed" value is changed
}
The problem is now if I touch a location the sprites rotates to it correctly but at first it rotates a little bit fast and than it looses it's speed..
The nearer it is to the destination angle the slower it moves...
But I want it to move at a constant speed. This "constant" speed is defined in the "smoothRotationSpeed" value but how can I implement it in my code?
If I set the interval to the "smoothRotationSpeed" value it wouldn't be smooth. It would lag.
Would anyone like to help me please?
You're incrementing the rotation of your sprite based on the difference between the target and current angles, so the closer the current angle is to the target angle, the smaller is the increment you're applying to the current rotation. But from what you're saying, that's not what you want. So you could simply do something like this:
-(void)rotateTick:(ccTime)dt {
startAngle_ = [self rotation];
if (startAngle_ > 0)
startAngle_ = fmodf(startAngle_, 360.0f);
else
startAngle_ = fmodf(startAngle_, -360.0f);
diffAngle_ =dstAngle_ - startAngle_;
if (diffAngle_ > 180)
diffAngle_ -= 360;
if (diffAngle_ < -180)
diffAngle_ += 360;
if ( fabs(diffAngle_) < smoothRotationSpeed_*dt ) // finished rotation
{
[self setRotation: dstAngle_]; // go to the end position
}
else if (diffAngle_ > 0)
{
[self setRotation: startAngle_ + smoothRotationSpeed_* dt]; //smooth rotate
}
else
{
[self setRotation: startAngle_ - smoothRotationSpeed_* dt]; //smooth rotate
}
}
-(void)setSmoothRotationSpeed:(float)smoothRotationSpeed
{
smoothRotationSpeed_ = smoothRotationSpeed;
[self schedule:#selector(rotateTick:)]; //this will be called if the "smoothRotationSpeed" value is changed
}