2010-08-23 7 views
9

Tengo cuatro UIViews en un UIScrollView (pantalla dividida en cuartiles)Finding objeto más cercano a CGPoint

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 al CGPoint dado?

¿Alguna idea?

Tengo el CGPoint y el marco (CGRect) de los objetos dentro de cada cuartil.

ACTUALIZACIÓN:

hRuler http://img.skitch.com/20100824-c333thq3rjcak9f5q5djjugjhw.preview.jpg
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 256x256. El primer CGPoint de UIImageView convertido al El sistema de coordenadas de UIScrollView (53.25, 399.36) debe estar muerto con el tapPoint (30,331). ¿Por qué la diferencia? El otro punto a la derecha del punto tocado es calcular más cerca (distancia sabia) ??

<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?????? 
+0

Esto es, básicamente, va a requerir un montón de matemáticas. ¿Tus UIImageViews son del mismo tamaño? –

+0

Sí, lo son. CGRect 26.624 71.68 32 39. Todas del mismo tamaño. – Jordan

+0

Actualización agregada con la imagen y el código de Kalle. Algo extraño sucede cuando convierto el CGPoint de UIImageView al sistema de coordenadas UIScrollViews que arroja las coordenadas y el código de Kalle. ¿Alguien puede ver lo que me estoy perdiendo? – Jordan

Respuesta

20

El siguiente método debería ser el truco. Si detecta algo raro en él, siéntase libre de 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 

que podría haber más versiones optimizadas por ahí, así que puede ser digno de excavación más.

El siguiente método determina la distancia entre dos CGPoints.

- (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: eliminado fabsf(); -x^2 es lo mismo que x^2, por lo que es innecesario.

Actualización 2: se agregó el método distanceBetweenPoint:andPoint: también, para compleción.

+0

Ok, esto se está poniendo interesante. Permítanme publicar un poco de código que convierta los objetos CGPoint en las coordenadas de UIScrollView para que el código de Kalle funcione. He notado un comportamiento extraño ... – Jordan

+0

¡Feliz de informar, SOVLED! Estaba usando [subview convertPoint: subview.frame.origin toView: self]; en lugar de [[superview de subvista] convertPoint: subview.frame.origin toView: self]; Volver a mi código original y todo funcionó bien. Pero me ayudaste a rastrear el error cometido. ¡Perfecto! Gracias Kalle !! ¡Te debo una! – Jordan

+0

Genial para escuchar :) Por curiosidad, ¿cómo lo resolvió originalmente? – Kalle

8

Si está utilizando Swift, así es como puede calcular la distancia entre un CGPoint y un CGRect (p.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) 
    } 
} 
1

Gracias @cristian,

Aquí está la versión de Objective-C de su respuesta

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

Shorter @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) 
} 

Personalmente, Implementaría esto como una extensión de 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) 
    } 
}