2010-08-23 3 views
9

J'ai quatre UIViews sur un UIScrollView (écran divisé en quartiles)objet le plus proche de CGPoint

Sur les quartiles, j'ai quelques objets (UIImageViews), sur chaque quartile.

Lorsque l'utilisateur appuie sur l'écran, je veux trouver l'objet le plus proche du CGPoint donné?

Des idées? J'ai le CGPoint et l'image (CGRect) des objets dans chaque quartile.

MISE À JOUR:

hRuler http://img.skitch.com/20100824-c333thq3rjcak9f5q5djjugjhw.preview.jpg
Pins rouges sont 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]); 

      } 

Console de débogage

est ici le problème. Chaque tuile est 256x256. Le CGPoint du premier UIImageView converti au système de coordonnées UIScrollView (53.25, 399.36) devrait être mort avec le tapPoint (30,331). Pourquoi la différence ?? L'autre point à la droite du point exploité est le calcul plus proche (distance sage) ??

<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

Cela va essentiellement d'exiger un tas de mathématiques. Vos UIImageViews sont-ils de la même taille? –

+0

Oui, ils le sont. CGRect 26.624 71.68 32 39. Tous de la même taille. – Jordan

+0

Ajout de la mise à jour avec Image et du code de Kalle. Il se passe quelque chose d'étrange quand je convertis le CGPoint de UIImageView en un système de coordonnées UIScrollViews qui rejette les coordonnées et le code de Kalle. Quelqu'un peut-il voir ce que je manque? – Jordan

Répondre

20

La méthode suivante devrait faire l'affaire. Si vous remarquez quelque chose de bizarre, n'hésitez pas à le signaler.

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

Exemple de sortie:

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 

Il pourrait être plus optimisé versions là-bas, donc peut-être la peine de creuser plus.

La méthode suivante détermine la distance entre deux points CG.

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

Mise à jour: suppression de fabsf(); -x^2 est identique à x^2, donc inutile.

Mise à jour 2: ajout de la méthode distanceBetweenPoint:andPoint: pour être complet.

+0

Ok, ça devient intéressant. Permettez-moi de poster un peu de code qui convertit les objets CGPoint dans les coordonnées de UIScrollView afin que le code de Kalle fonctionne. J'ai remarqué un comportement étrange ... – Jordan

+0

Heureux de signaler, SOVLED! J'utilisais [subview convertPoint: subview.frame.origin toView: self]; au lieu de [[subview superview]] convertPoint: subview.frame.origin toView: self]; Retour à mon code original et tout a bien fonctionné. Mais tu m'as aidé à faire le suivi de l'erreur. Parfait! Merci Kalle !! Je t'en dois une! – Jordan

+0

Super pour entendre :) Par curiosité, comment avez-vous résolu le problème à l'origine? – Kalle

8

Si vous utilisez Swift, voici comment calculer la distance entre un CGPoint et un CGRect (par ex.un châssis de 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

Merci @cristian,

est ici la version Objective-C de votre réponse

- (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 réponse:

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

Personnellement, Je voudrais implémenter cela comme une extension 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) 
    } 
} 

Vous pouvez également mettre en œuvre comme une extension 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) 
    } 
}