Cocoa (not touch) collision detection of two circles - objective-c

I am trying to detect the collision of two circles. The process works when a user creates circles (button click) and then mouse clicks on one of the circles to activate it. Mouse clicking anywhere outside the active circle will move the circle to the x,y of the mouse click. What I want to do is stop the movement if there is another circle within the path of the active circle.
NSPoint P = the mouse click (passed in through a notification center)
activeView = the selected circle (subclass of NSView)
My process is to get the point to move to, determine the direction of movement based on the start and end points and also the number of increments for the x start and end points, loop through the increments, use the animator method of NSView to move the view, loop through all non-active views (all the other circles) and compare their position to the current position of the active view using the formula ((x1*x2)+(x1*x2)) + (y1*y2)+(y1*y2) and comparing to the square of the radius of both circles (the circles are in a 30x30 rect with a 4 pixel stroke on the circles, so I used 26 for my radius (26*26) check.
I have a couple of issues (aside from my desire to reinvent the wheel : D - this is a learning exercise for me):
1: The movement code part actually works great. However, I'd like to get the center point of the circle on the mouse click, right now the circle is stopping so that the NSRect that contains it (30x30) x,y coordinate is at the mouse click (this is not flipped, so its lower left).
2: I am getting some collision detection but this seems to be only when the x,y of both containing rects intersect, not the circle edges.
3: Unrelated to all this, I have the NSColor for the containing rects set to clearColor and they initially start out that way but then when I move them they revert back to the default color (that gray color). Do I have to assign the clearColor each time the view is redrawn?
Thanks for the help.
hasCollision = NO;
//get point clicked value
NSValue* pVal = [[notification userInfo] objectForKey:#"dataPoint"];
//convert value to point (end point)
NSPoint p = [pVal pointValue];
//get activeView frame
NSRect avFrame = [activeView frame];
float startX = avFrame.origin.x; //x1
float startY = avFrame.origin.y; //y1
float endX = p.x; //x2
float endY = p.y; //y2
//set direction of activeView movement
if (endX > startX && endY > startY) {
//NSLog(#"endXY > startXY");
cStart = startX;
cCheck =endX;
distanceX = endX - startX;
incrementY = (endY - startY)/distanceX;
} else if (endX < startX && endY > startY) {
//NSLog(#"endX<startX, endY>startY");
cStart = endX;
cCheck =startX;
distanceX = startX - endX;
incrementY = (endY - startY)/distanceX;
} else if (endX < startX && endY < startY) {
//NSLog(#"endX<startX, endY<startY");
cStart = endX;
cCheck =startX;
distanceX = startX - endX;
incrementY = (startY - endY)/distanceX;
} else {
//NSLog(#"endX>startX, endY<startY");
cStart = startX;
cCheck =endX;
distanceX = endX - startX;
incrementY = (startY - endY)/distanceX;
}
//NSLog(#"Start:%f:%f:End%f:%f:::distance=%f:::Y Increment=%f", startX, startY, endX, endY, distanceX, incrementY);
float currentX = startX;
float currentY = startY;
for(int c=cStart; c<cCheck; c++) {
//get move coordinates
if (endX > startX && endY > startY) {
currentX++;
currentY = currentY + incrementY;
} else if (endX < startX && endY > startY) {
currentX--;
currentY = currentY + incrementY;
} else if (endX < startX && endY < startY) {
currentX--;
currentY = currentY - incrementY;
} else {
currentX++;
currentY = currentY - incrementY;
}
//move view
[[activeView animator] setFrame:NSMakeRect(currentX, currentY, avFrame.size.width, avFrame.size.height)];
//make a rect for each object
NSRect avRect = [activeView frame];
//store previous positions
prevPos = [activeView frame];
//check for collisions
for(int v=0; v<[createdViewsDict count]; v++) {
//get the id of the view in the dict
NSString* nextNum = [NSString stringWithFormat:#"circle_%i", v+1];
//init a view and assign it to the next view in the dict
NSView* thisView = [createdViewsDict objectForKey:nextNum];
//make sure this view is not our active view
if (thisView != activeView) {
NSRect tvRect = [thisView frame];
//get the xy coordinates to compare
float x1 = avRect.origin.x;
float y1 = avRect.origin.y;
float x2 = tvRect.origin.x;
float y2 = tvRect.origin.y;
float radius = 26.0;
float dx = x2-x1;
float dy = y2-y1;
float radii = radius + radius;
if ( ( dx * dx ) + ( dy * dy ) < radii * radii ) {
hasCollision = YES;
//[[activeView animator] setFrame:NSMakeRect(prevPos.origin.x, prevPos.origin.y, avFrame.size.width, avFrame.size.height)];
NSLog(#"Collision!!Moving object coords:x=%f,y=%f,:::::::::Stationary object coords:x=%f,y=%f", avRect.origin.x, avRect.origin.y, tvRect.origin.x, tvRect.origin.y);
}
/*
//check for collision
NSRect collisionRect = NSIntersectionRect(avRect, tvRect);
//move circle to previous
if ( !NSIsEmptyRect(collisionRect) ) {
hasCollision = YES;
[[activeView animator] setFrame:NSMakeRect(prevPos.origin.x, prevPos.origin.y, avFrame.size.width, avFrame.size.height)];
NSLog(#"Collision!!Moving object coords:x=%f,y=%f,:::::::::Stationary object coords:x=%f,y=%f", avRect.origin.x, avRect.origin.y, tvRect.origin.x, tvRect.origin.y);
}
*/
}
}
if(hasCollision == YES) {
[[activeView animator] setFrame:NSMakeRect(prevPos.origin.x - 20.0, prevPos.origin.y - 20.0, avFrame.size.width, avFrame.size.height)];
hasCollision = NO;
break;
}
}

Related

Rotate image in bounds

I try to rotate an image view in its superview so that this image view while rotating always touches superview's borders not crossing them, with appropriate resizing. How can I implement this? The image view should be able to rotate around 360˚.
Here I use calculations based on triangle formulas, considering initial image view diagonal angle.
Maybe I should take into account new bounding frame of the image view after it gets rotated (its x and y coordinates get negative and its frame size after transform gets bigger too).
No success so far, my image view gets sized down too quickly and too much. So my goal as I understand to get proper scale factor for CGAffineTransformScale. Maybe there are other ways to do the same.
// set initial values
_planImageView.layer.affineTransform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1);
_degrees = 0;
_initialWidth = _planImageView.frame.size.width;
_initialHeight = _planImageView.frame.size.height;
_initialAngle = MathUtils::radiansToDegrees(atan((_initialWidth / 2) / (_initialHeight / 2)));
// rotation routine
- (void)rotatePlanWithDegrees:(double)degrees
{
double deltaDegrees = degrees - _degrees;
_initialAngle -= deltaDegrees;
double newAngle = _initialAngle;
double newWidth = (_initialWidth / 2) * tan(MathUtils::degreesToRadians(newAngle)) * 2;
double newHeight = newWidth * (_initialHeight / _initialWidth);
NSLog(#"DEG %f DELTA %f A %f W %f H %f", degrees, deltaDegrees, newAngle, newWidth, newHeight);
double currentScale = newWidth / _initialWidth;
_planImageView.layer.affineTransform = CGAffineTransformScale(CGAffineTransformIdentity, currentScale, currentScale);
_planImageView.layer.affineTransform = CGAffineTransformRotate(_planImageView.layer.affineTransform, (CGFloat) MathUtils::degreesToRadians(degrees));
_degrees = degrees;
self->_planImageView.center = _center;
// NSLog(#"%#", NSStringFromCGRect(_planImageView.frame));
}
EDIT
I overwrote routine thanks to the answer and now it works!
- (void)rotatePlanWithDegrees:(double)degrees
{
double newWidth =
_initialWidth * abs(cos(MathUtils::degreesToRadians(degrees))) +
_initialHeight * abs(sin(MathUtils::degreesToRadians(degrees)));
double newHeight =
_initialWidth * abs(sin(MathUtils::degreesToRadians(degrees))) +
_initialHeight * abs(cos(MathUtils::degreesToRadians(degrees)));
CGFloat scale = (CGFloat) MIN(
self.planImageScrollView.frame.size.width / newWidth,
self.planImageScrollView.frame.size.height / newHeight);
CGAffineTransform rotationTransform = CGAffineTransformMakeRotation((CGFloat) MathUtils::degreesToRadians(degrees));
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scale, scale);
_planImageView.layer.affineTransform = CGAffineTransformConcat(rotationTransform, scaleTransform);
self->_planImageView.center = _center;
}
When you rotate a rectangle W x H, the bounding box takes the dimensions W' = W |cos Θ| + H |sin Θ|, H' = W |sin Θ| + H |cos Θ|.
If you need to fit that in a W" x H" rectangle, the scaling factor is the smallest of W"/W' and H"/H'.

Could it be the touch of two fingers on the screen call handlePan method?

I have a problem with My code , I need to handle pan or pinch on screen.
but sometimes the handlePanl method call when My two finger on screen.
What Am I doing wrong ?
-(void)addGestureRecognizer
{
// handle Pinch
imageViewForCrop.userInteractionEnabled = YES;
UIPinchGestureRecognizer *PinchOnScreen = [[UIPinchGestureRecognizer alloc]
initWithTarget:self action:#selector(handlePinch:)];
[imageViewForCrop addGestureRecognizer:PinchOnScreen];
// handle Pan
UIPanGestureRecognizer *panOnScreen = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[imageViewForCrop addGestureRecognizer:panOnScreen];
}
This is Pan method :
-(void)handlePan:(UIPanGestureRecognizer *)recognizer
{
switch (recognizer.state)
{
case UIGestureRecognizerStateChanged:
{
NSLog(#"%lu",(unsigned long)recognizer.numberOfTouches);
CGPoint translation = [recognizer translationInView:imageViewForCrop];
if (image.size.width > image.size.height) // weight image
{
// after user pinch in the screen
if (imageViewForCrop.frame.size.height > self.view.frame.size.height - bottomImageCropView.frame.size.height)
{
// allow dragging only in Y coordinates by only updating the Y coordenates with translation position
recognizer.view.center = CGPointMake(recognizer.view.center.x, recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:imageViewForCrop];
// get the top edge coordinate for the top left corner of crop frame
float topEdgeImagePosition = CGRectGetMinY(imageViewForCrop.frame);
float topEdgeCropEreaPosition = CGRectGetMinY(cropErea.frame);
//get the buttom edge coordinate for bottom left corner of crop frame
float bottomEdgeImagePosition = CGRectGetMaxY(imageViewForCrop.frame);
float bottomEdgeCropEreaPosition = CGRectGetMaxY(cropErea.frame);
//
// if the top edge coordinate is less than or equal to Crop Erea frame
if (topEdgeImagePosition > topEdgeCropEreaPosition)
{
// draw drag view in max top position
imageViewForCrop.frame = CGRectMake(imageViewForCrop.frame.origin.x,topEdgeCropEreaPosition,imageViewForCrop.frame.size.width,imageViewForCrop.frame.size.height);
}
// if bottom edge coordinate is greater than or equal to 480
if (bottomEdgeImagePosition < bottomEdgeCropEreaPosition)
{
float ratio = (imageViewForCrop.frame.size.height - cropErea.frame.size.height) * -1;
// draw drag view in max bottom position
imageViewForCrop.frame = CGRectMake(imageViewForCrop.frame.origin.x,ratio,imageViewForCrop.frame.size.width,imageViewForCrop.frame.size.height);
}
}
// allow dragging only in X coordinates by only updating the X coordenates with translation position
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x, recognizer.view.center.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:imageViewForCrop];
// get the top edge coordinate for the top left corner of crop frame
float leftEdgeImagePosition = CGRectGetMinX(imageViewForCrop.frame);
float leftEdgeCropEreaPosition = CGRectGetMinX(cropErea.frame);
//get the buttom edge coordinate for bottom left corner of crop frame
float rightEdgeImagePosition = CGRectGetMaxX(imageViewForCrop.frame);
float rightEdgeCropEreaPosition = CGRectGetMaxX(cropErea.frame);
//
// if the top edge coordinate is less than or equal to Crop Erea frame
if (leftEdgeImagePosition > leftEdgeCropEreaPosition)
{
// draw drag view in max top position
imageViewForCrop.frame = CGRectMake(leftEdgeCropEreaPosition,0,imageViewForCrop.frame.size.width,imageViewForCrop.frame.size.height);
}
// if bottom edge coordinate is greater than or equal to 480
if (rightEdgeImagePosition < rightEdgeCropEreaPosition)
{
float ratio = (imageViewForCrop.frame.size.width - cropErea.frame.size.width) * -1;
// draw drag view in max bottom position
imageViewForCrop.frame = CGRectMake(ratio,0,imageViewForCrop.frame.size.width,imageViewForCrop.frame.size.height);
}
}
else if (image.size.width < image.size.height) // Height image
{
// after user pinch in the screen
if (imageViewForCrop.frame.size.width > self.view.frame.size.width)
{
// allow dragging only in X coordinates by only updating the X coordenates with translation position
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x, recognizer.view.center.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:imageViewForCrop];
// get the top edge coordinate for the top left corner of crop frame
float leftEdgeImagePosition = CGRectGetMinX(imageViewForCrop.frame);
float leftEdgeCropEreaPosition = CGRectGetMinX(cropErea.frame);
//get the buttom edge coordinate for bottom left corner of crop frame
float rightEdgeImagePosition = CGRectGetMaxX(imageViewForCrop.frame);
float rightEdgeCropEreaPosition = CGRectGetMaxX(cropErea.frame);
//
// if the top edge coordinate is less than or equal to Crop Erea frame
if (leftEdgeImagePosition > leftEdgeCropEreaPosition)
{
// draw drag view in max top position
imageViewForCrop.frame = CGRectMake(leftEdgeCropEreaPosition,imageViewForCrop.frame.origin.y,imageViewForCrop.frame.size.width,imageViewForCrop.frame.size.height);
}
// if bottom edge coordinate is greater than or equal to 480
if (rightEdgeImagePosition < rightEdgeCropEreaPosition)
{
float ratio = (imageViewForCrop.frame.size.width - cropErea.frame.size.width) * -1;
// draw drag view in max bottom position
imageViewForCrop.frame = CGRectMake(ratio,imageViewForCrop.frame.origin.y,imageViewForCrop.frame.size.width,imageViewForCrop.frame.size.height);
}
}
// allow dragging only in Y coordinates by only updating the Y coordenates with translation position
recognizer.view.center = CGPointMake(recognizer.view.center.x, recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:imageViewForCrop];
// get the top edge coordinate for the top left corner of crop frame
float topEdgeImagePosition = CGRectGetMinY(imageViewForCrop.frame);
float topEdgeCropEreaPosition = CGRectGetMinY(cropErea.frame);
//get the buttom edge coordinate for bottom left corner of crop frame
float bottomEdgeImagePosition = CGRectGetMaxY(imageViewForCrop.frame);
float bottomEdgeCropEreaPosition = CGRectGetMaxY(cropErea.frame);
//
// if the top edge coordinate is less than or equal to Crop Erea frame
if (topEdgeImagePosition > topEdgeCropEreaPosition)
{
// draw drag view in max top position
imageViewForCrop.frame = CGRectMake(0,topEdgeCropEreaPosition,imageViewForCrop.frame.size.width,imageViewForCrop.frame.size.height);
}
// if bottom edge coordinate is greater than or equal to 480
if (bottomEdgeImagePosition < bottomEdgeCropEreaPosition)
{
float ratio = (imageViewForCrop.frame.size.height - cropErea.frame.size.height) * -1;
// draw drag view in max bottom position
imageViewForCrop.frame = CGRectMake(0,ratio,imageViewForCrop.frame.size.width,imageViewForCrop.frame.size.height);
}
}
else
{
imageViewForCrop.frame = CGRectMake(cropErea.frame.origin.x, cropErea.frame.origin.y, 320, 320);
}
}
default:
break;
}
}
This is pinch method :
- (void)handlePinch:(UIPinchGestureRecognizer *)pinchGestureRecognizer
{
NSLog(#" X %i " ,(int)imageViewForCrop.frame.origin.x );
if ((int)imageViewForCrop.frame.origin.x > 0 )
{
[UIView animateWithDuration:0.4 animations:^
{
[imageViewForCrop setFrame:CGRectMake( 0 , imageViewForCrop.frame.origin.y , imageViewForCrop.frame.size.width , imageViewForCrop.frame.size.height )];
}];
}
NSLog(#" Y %i " , (int)imageViewForCrop.frame.origin.y );
if ((int)imageViewForCrop.frame.origin.y > 0)
{
[UIView animateWithDuration:0.4 animations:^
{
[imageViewForCrop setFrame:CGRectMake( imageViewForCrop.frame.origin.x , 0 , imageViewForCrop.frame.size.width , imageViewForCrop.frame.size.height )];
}];
}
NSLog(#" Width %i and %i " , (int)imageViewForCrop.frame.size.width , (int)originalSizeForIphoneWidth );
if ((int)imageViewForCrop.frame.size.width < (int)originalSizeForIphoneWidth)
{
[UIView animateWithDuration:0.4 animations:^
{
[imageViewForCrop setFrame:CGRectMake( imageViewForCrop.frame.origin.x , imageViewForCrop.frame.origin.y , originalSizeForIphoneWidth+1, imageViewForCrop.frame.size.height)];
}];
}
NSLog(#"Hight %i and %i " , (int)imageViewForCrop.frame.size.height , (int)originalSizeForIphoneHight );
if ((int)imageViewForCrop.frame.size.height < (int)originalSizeForIphoneHight)
{
[UIView animateWithDuration:0.4 animations:^
{
[imageViewForCrop setFrame:CGRectMake(imageViewForCrop.frame.origin.x , imageViewForCrop.frame.origin.y , imageViewForCrop.frame.size.width, originalSizeForIphoneHight+1)];
}];
//return;
}
CGFloat factor = [(UIPinchGestureRecognizer *) pinchGestureRecognizer scale];
if(factor >1)
{
// for the first time bug when pinch in or out
if (lastScaleFactor == 0 )
{
lastScaleFactor = 1;
}
imageViewForCrop.transform =CGAffineTransformMakeScale(lastScaleFactor +(factor-1), lastScaleFactor +(factor-1));
}
else // pinch in
{
imageViewForCrop.transform=CGAffineTransformMakeScale(lastScaleFactor * factor, lastScaleFactor*factor);
}
if (pinchGestureRecognizer.state == UIGestureRecognizerStateEnded)
{
if (factor>1) {
lastScaleFactor +=(factor-1);
}
else {
lastScaleFactor*= factor;
}
}
}
I solved the problem with limit maximumNumberOfTouches:
// handle Pan
UIPanGestureRecognizer *panOnScreen = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
panOnScreen.maximumNumberOfTouches = 1;
[imageViewForCrop addGestureRecognizer:panOnScreen];
I hope this help someone (-: .

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

zooming using pinch but not using transform methods

I'm trying to zoom the drawing on the context based on pinching in and out. This is working perfectly, but it will always zoom from the origin (top left). This doesn't give the nice effect you get in other applications and is really annoying. The old zooming method is as follows:
- (IBAction)handlePinchGesture:(UIGestureRecognizer *)sender {
UIPinchGestureRecognizer *pinch = (UIPinchGestureRecognizer *)sender;
if ([pinch scale] > zoomLevel + MAX_ZOOM_PER_CALL) {
[pinch setScale: zoomLevel + MAX_ZOOM_PER_CALL];
}
if ([pinch scale] < zoomLevel - MAX_ZOOM_PER_CALL) {
[pinch setScale:zoomLevel - MAX_ZOOM_PER_CALL];
}
float prevZoomLevel = zoomLevel;
CGFloat factor = [pinch scale];
zoomLevel = factor;
if (zoomLevel < MIN_ZOOM_LEVEL) {
zoomLevel = MIN_ZOOM_LEVEL;
}
if (zoomLevel > MAX_ZOOM_LEVEL) {
zoomLevel = MAX_ZOOM_LEVEL;
}
//The next piece of code is added here
[self setNeedsDisplay];
}
The actual zooming is done in the drawing methods of the parts that are shown on the context. I was thinking of changing the origin of the screen (that is the relative origin to the items that are to be drawn) so it would simulate this. But it is not working at all, this is what i have right now:
float dZoomLevel = zoomLevel - prevZoomLevel;
if (dZoomLevel != 0) {
CGPoint touchLocation = [pinch locationInView:self];
CGPoint zoomedTouchLocation = CGPointMake(touchLocation.x * prevZoomLevel, touchLocation.y * prevZoomLevel);
CGPoint newLocation = CGPointMake(touchLocation.x * zoomLevel, touchLocation.y * zoomLevel);
float dX = ( newLocation.x - zoomedTouchLocation.x );
float dY = ( newLocation.y - zoomedTouchLocation.y );
_originX += roundf(dX);
_originY += roundf(dY);
}
The origin is taken from the xml (the smallest x and y of all the elements). It will usually be something between 12000 and 20000 but could in theory be between 0 and 100000.
I'm not sure if I've made myself clear, but if you need any information or don't understand something just ask me and i'll try to reply ASAP. Thanks in advance!
I found the answer myself. The way one needs to zoom in my case is by zooming from the point of the touch. So when the x and y coordinates get zoomed do this:
x -= pinchLocation.x;
y -= pinchLocation.y;
x *= zoomLevel;
y *= zoomLevel;
x += pinchLocation.x;
y += pinchLocation.y;
or in short:
x = (x - pinchLocation.x) * zoomLocation + pinchLocation.x;
y = (y - pinchLocation.y) * zoomLocation + pinchLocation.y;
now x and y are on the correct location!!

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.