Encontrar el objeto más cercano a CGPoint
Pregunta
Tengo cuatro UIViews en un UIScrollView (pantalla dividida en cuartiles)
En los cuartiles, tengo un par de objetos (UIImageViews), en cada cuartil.
Cuando el usuario toca la pantalla, quiero encontrar el objeto más cercano a la CGPoint dado?
Algunas ideas?
Tengo el CGPoint y el marco (CGRect) de los objetos dentro de cada cuartil.
ACTUALIZACIÓN:
(fuente: skitch.com )
rojo botones son UIImageViews.
// UIScrollView
NSLog(@" UIScrollView: %@", self);
// Here's the tap on the Window in UIScrollView's coordinates
NSLog(@"TapPoint: %3.2f, %3.2f", tapLocation.x, tapLocation.y);
// Find Distance between tap and objects
NSArray *arrayOfCGRrectObjects = [self subviews];
NSEnumerator *enumerator = [arrayOfCGRrectObjects objectEnumerator];
for (UIView *tilesOnScrollView in enumerator) {
// each tile may have 0 or more images
for ( UIView *subview in tilesOnScrollView.subviews ) {
// Is this an UIImageView?
if ( [NSStringFromClass([subview class]) isEqualToString:@"UIImageView"]) {
// Yes, here are the UIImageView details (subView)
NSLog(@"%@", subview);
// Convert CGPoint of UIImageView to CGPoint of UIScrollView for comparison...
// First, Convert CGPoint from UIScrollView to UIImageView's coordinate system for reference
CGPoint found = [subview convertPoint:tapLocation fromView:self];
NSLog(@"Converted Point from ScrollView: %3.2f, %3.2f", found.x, found.y);
// Second, Convert CGPoint from UIScrollView to Window's coordinate system for reference
found = [subview convertPoint:subview.frame.origin toView:nil];
NSLog(@"Converted Point in Window: %3.2f, %3.2f", found.x, found.y);
// Finally, use the object's CGPoint in UIScrollView's coordinates for comparison
found = [subview convertPoint:subview.frame.origin toView:self]; // self is UIScrollView (see above)
NSLog(@"Converted Point: %3.2f, %3.2f", found.x, found.y);
// Determine tap CGPoint in UIImageView's coordinate system
CGPoint localPoint = [touch locationInView:subview];
NSLog(@"LocateInView: %3.2f, %3.2f",localPoint.x, localPoint.y );
//Kalle's code
CGRect newRect = CGRectMake(found.x, found.y, 32, 39);
NSLog(@"Kalle's Distance: %3.2f",[self distanceBetweenRect:newRect andPoint:tapLocation]);
}
Consola de depuración
Aquí está el problema. Cada azulejo es de 256x256. La primera CGPoint de UIImageView convierte en el sistema de coordenadas del UIScrollView (53.25, 399.36) debe ser muerto en la TapPoint (30331). ¿Por qué la diferencia ?? El otro punto a la derecha del punto roscado está calculando más cerca (distancia prudente) ??
<CALayer: 0x706a690>>
[207] TapPoint: 30.00, 331.00
[207] <UIImageView: 0x7073db0; frame = (26.624 71.68; 32 39); opaque = NO; userInteractionEnabled = NO; tag = 55; layer = <CALayer: 0x70747d0>>
[207] Converted Point from ScrollView: 3.38, 3.32
[207] Converted Point in Window: 53.25, 463.36
[207] Converted Point: 53.25, 399.36 *** Looks way off!
[207] LocateInView: 3.38, 3.32
[207] Kalle's Distance: 72.20 **** THIS IS THE TAPPED POINT
[207] <UIImageView: 0x7074fb0; frame = (41.984 43.008; 32 39); opaque = NO; userInteractionEnabled = NO; tag = 55; layer = <CALayer: 0x7074fe0>>
[207] Converted Point from ScrollView: -11.98, 31.99
[207] Converted Point in Window: 83.97, 406.02
[207] Converted Point: 83.97, 342.02
[207] LocateInView: -11.98, 31.99
207] Kalle's Distance: 55.08 ***** BUT THIS ONE's CLOSER??????
Solución
El siguiente método debe hacer el truco. Si ves algo raro en ella sensación libremente para señalarlo.
- (CGFloat)distanceBetweenRect:(CGRect)rect andPoint:(CGPoint)point
{
// first of all, we check if point is inside rect. If it is, distance is zero
if (CGRectContainsPoint(rect, point)) return 0.f;
// next we see which point in rect is closest to point
CGPoint closest = rect.origin;
if (rect.origin.x + rect.size.width < point.x)
closest.x += rect.size.width; // point is far right of us
else if (point.x > rect.origin.x)
closest.x = point.x; // point above or below us
if (rect.origin.y + rect.size.height < point.y)
closest.y += rect.size.height; // point is far below us
else if (point.y > rect.origin.y)
closest.y = point.y; // point is straight left or right
// we've got a closest point; now pythagorean theorem
// distance^2 = [closest.x,y - closest.x,point.y]^2 + [closest.x,point.y - point.x,y]^2
// i.e. [closest.y-point.y]^2 + [closest.x-point.x]^2
CGFloat a = powf(closest.y-point.y, 2.f);
CGFloat b = powf(closest.x-point.x, 2.f);
return sqrtf(a + b);
}
Ejemplo de salida:
CGPoint p = CGPointMake(12,12);
CGRect a = CGRectMake(5,5,10,10);
CGRect b = CGRectMake(13,11,10,10);
CGRect c = CGRectMake(50,1,10,10);
NSLog(@"distance p->a: %f", [self distanceBetweenRect:a andPoint:p]);
// 2010-08-24 13:36:39.506 app[4388:207] distance p->a: 0.000000
NSLog(@"distance p->b: %f", [self distanceBetweenRect:b andPoint:p]);
// 2010-08-24 13:38:03.149 app[4388:207] distance p->b: 1.000000
NSLog(@"distance p->c: %f", [self distanceBetweenRect:c andPoint:p]);
// 2010-08-24 13:39:52.148 app[4388:207] distance p->c: 38.013157
Puede haber más versiones por ahí optimizado, por lo que podría valer la pena una excavación más.
El siguiente método determina la distancia entre dos PuntosCg.
- (CGFloat)distanceBetweenPoint:(CGPoint)a andPoint:(CGPoint)b
{
CGFloat a2 = powf(a.x-b.x, 2.f);
CGFloat b2 = powf(a.y-b.y, 2.f);
return sqrtf(a2 + b2)
}
Actualización: fabsf Eliminado (); -x ^ 2 es el mismo que x ^ 2, por lo que es innecesario.
Actualización 2:. Método distanceBetweenPoint:andPoint:
añadido también, para la integridad
Otros consejos
Si está utilizando Swift, así es como se puede calcular la distancia entre un CGPoint y una CGRect (por ejemplo, el marco de un UIView)
private func distanceToRect(rect: CGRect, fromPoint point: CGPoint) -> CGFloat {
// if it's on the left then (rect.minX - point.x) > 0 and (point.x - rect.maxX) < 0
// if it's on the right then (rect.minX - point.x) < 0 and (point.x - rect.maxX) > 0
// if it's inside the rect then both of them < 0.
let dx = max(rect.minX - point.x, point.x - rect.maxX, 0)
// same as dx
let dy = max(rect.minY - point.y, point.y - rect.maxY, 0)
// if one of them == 0 then the distance is the other one.
if dx * dy == 0 {
return max(dx, dy)
} else {
// both are > 0 then the distance is the hypotenuse
return hypot(dx, dy)
}
}
Gracias @cristian,
Aquí está la versión de Objective-C de la respuesta ??p>
- (CGFloat)distanceToRect:(CGRect)rect fromPoint:(CGPoint)point
{
CGFloat dx = MAX(0, MAX(CGRectGetMinX(rect) - point.x, point.x - CGRectGetMaxX(rect)));
CGFloat dy = MAX(0, MAX(CGRectGetMinY(rect) - point.y, point.y - CGRectGetMaxY(rect)));
if (dx * dy == 0)
{
return MAX(dx, dy);
}
else
{
return hypot(dx, dy);
}
}
Más corto @cristian Respuesta:
func distance(from rect: CGRect, to point: CGPoint) -> CGFloat {
let dx = max(rect.minX - point.x, point.x - rect.maxX, 0)
let dy = max(rect.minY - point.y, point.y - rect.maxY, 0)
return dx * dy == 0 ? max(dx, dy) : hypot(dx, dy)
}
En lo personal, me gustaría implementar esto como una extensión CGPoint:
extension CGPoint {
func distance(from rect: CGRect) -> CGFloat {
let dx = max(rect.minX - x, x - rect.maxX, 0)
let dy = max(rect.minY - y, y - rect.maxY, 0)
return dx * dy == 0 ? max(dx, dy) : hypot(dx, dy)
}
}
Como alternativa, también se puede aplicar como una extensión CGRect:
extension CGRect {
func distance(from point: CGPoint) -> CGFloat {
let dx = max(minX - point.x, point.x - maxX, 0)
let dy = max(minY - point.y, point.y - maxY, 0)
return dx * dy == 0 ? max(dx, dy) : hypot(dx, dy)
}
}