2013-07-21 17 views
22

Ich entwickle App, wo Benutzer von GPS lokalisiert wird und dann gefragt wird, ob er an einem bestimmten Ort befindet. Um dies zu bestätigen, wird ihm sofort eine Callout-Blase präsentiert, die ihn fragt, ob er sich an einem bestimmten Ort befindet.Benutzerdefinierte MKAnnotation Callout Blase mit Button

Da es eine Menge von ähnlichen Fragen ist, ich war in der Lage kundenspezifische Hinweisblase zu tun: Custom callout bubble

Mein Problem: Taste ist nicht „klickbar“ Meine Vermutung: weil dieser Brauch callout höher ist als Standard Hinweisblase Ich musste es in einen negativen "Rahmen" stellen, daher kann der Knopf nicht angeklickt werden. Hier ist meine didSelectAnnotationView Methode

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view { 
    if(![view.annotation isKindOfClass:[MKUserLocation class]]) { 
     CalloutView *calloutView = (CalloutView *)[[[NSBundle mainBundle] loadNibNamed:@"callOutView" owner:self options:nil] objectAtIndex:0]; 
     CGRect calloutViewFrame = calloutView.frame; 
     calloutViewFrame.origin = CGPointMake(-calloutViewFrame.size.width/2 + 15, -calloutViewFrame.size.height); 
     calloutView.frame = calloutViewFrame; 
     [calloutView.calloutLabel setText:[(MyLocation*)[view annotation] title]]; 
     [calloutView.btnYes addTarget:self 
           action:@selector(checkin) 
        forControlEvents:UIControlEventTouchUpInside]; 
     calloutView.userInteractionEnabled = YES; 
     view.userInteractionEnabled = YES; 
     [view addSubview:calloutView]; 
    } 

} 

CalloutView ist nur einfache Klasse mit 2 Eigenschaften (Label, Name des Ortes und der Taste zeigt) und mit xib.

Ich habe diese benutzerdefinierte Callout-Blase für ein paar Tage gemacht. Ich habe versucht, mit "asynchrone Lösungen" solution, aber ich konnte keine andere Art von Button und dann Offenlegung Schaltfläche hinzufügen.

Mein nächster Versuch war, etwas zu finden, das einfacher als asynchrone Lösungen war, und es zu meiner Verwendung zu modifizieren. So habe ich tochi's custom callout gefunden.

Tochi's custom callout

Auf der Grundlage seiner Arbeit, ich konnte seine Blase anpassen und Info-Taste für meine benutzerdefinierte Schaltfläche ändern. Mein Problem ist jedoch gleich geblieben. Um meine benutzerdefinierte Callout-Ansicht auf den Pin zu setzen, musste ich ihr einen negativen Frame geben, so dass meine Schaltfläche nur in den unteren 5 Pixeln "anklickbar" war. Es scheint, dass ich vielleicht tiefer in die Standard-Callout-Blase von ios eindringen, sie untergliedern und den Callout-Rahmen dort ändern muss. Aber ich bin jetzt wirklich hoffnungslos.

Wenn ihr mir den richtigen Weg zeigen könnt oder mir Ratschläge gibt, werde ich mich freuen.

Antwort

75

Es gibt mehrere Ansätze zur Anpassung von Beschriftungen:

  1. Der einfachste Ansatz ist es, die bestehenden rechten und linken callout Zubehör zu verwenden, und setzen Sie Ihre Schaltfläche in einer von denen.Zum Beispiel:

    - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation { 
        static NSString *identifier = @"MyAnnotationView"; 
    
        if ([annotation isKindOfClass:[MKUserLocation class]]) { 
         return nil; 
        } 
    
        MKPinAnnotationView *view = (id)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier]; 
        if (view) { 
         view.annotation = annotation; 
        } else { 
         view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier]; 
         view.canShowCallout = true; 
         view.animatesDrop = true; 
         view.rightCalloutAccessoryView = [self yesButton]; 
        } 
    
        return view; 
    } 
    
    - (UIButton *)yesButton { 
        UIImage *image = [self yesButtonImage]; 
    
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 
        button.frame = CGRectMake(0, 0, image.size.width, image.size.height); // don't use auto layout 
        [button setImage:image forState:UIControlStateNormal]; 
        [button addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventPrimaryActionTriggered]; 
    
        return button; 
    } 
    
    - (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control { 
        NSLog(@"%s", __PRETTY_FUNCTION__); 
    } 
    

    Das ergibt:

    enter image description here

  2. Wenn Sie wirklich auf der rechten Seite die Schaltfläche nicht mögen, wo Zubehör im Allgemeinen gehen, können Sie das Zubehör ausschalten und iOS 9 bietet die Möglichkeit, die detailCalloutAccessoryView zu spezifizieren, die die Legende der Untertitel ersetzt mit dem, was gewünschte Ansicht:

    - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation { 
        static NSString *identifier = @"MyAnnotationView"; 
    
        if ([annotation isKindOfClass:[MKUserLocation class]]) { 
         return nil; 
        } 
    
        MKPinAnnotationView *view = (id)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier]; 
        if (view) { 
         view.annotation = annotation; 
        } else { 
         view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier]; 
         view.canShowCallout = true; 
         view.animatesDrop = true; 
        } 
        view.detailCalloutAccessoryView = [self detailViewForAnnotation:annotation]; 
    
        return view; 
    } 
    
    - (UIView *)detailViewForAnnotation:(PlacemarkAnnotation *)annotation { 
        UIView *view = [[UIView alloc] init]; 
        view.translatesAutoresizingMaskIntoConstraints = false; 
    
        UILabel *label = [[UILabel alloc] init]; 
        label.text = annotation.placemark.name; 
        label.font = [UIFont systemFontOfSize:20]; 
        label.translatesAutoresizingMaskIntoConstraints = false; 
        label.numberOfLines = 0; 
        [view addSubview:label]; 
    
        UIButton *button = [self yesButton]; 
        [view addSubview:button]; 
    
        NSDictionary *views = NSDictionaryOfVariableBindings(label, button); 
    
        [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[label]|" options:0 metrics:nil views:views]]; 
        [view addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]]; 
        [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[label]-[button]|" options:0 metrics:nil views:views]]; 
    
        return view; 
    } 
    
    - (UIButton *)yesButton { 
        UIImage *image = [self yesButtonImage]; 
    
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 
        button.translatesAutoresizingMaskIntoConstraints = false; // use auto layout in this case 
        [button setImage:image forState:UIControlStateNormal]; 
        [button addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventPrimaryActionTriggered]; 
    
        return button; 
    } 
    

    Dies ergibt:

    enter image description here

  3. Wenn Sie wirklich ein benutzerdefiniertes Callout selbst entwickeln wollen, die Location and Maps Programming Guide umreißt die Schritte:

    In einem iOS-App, ist es empfehlenswert, die mapView:annotationView:calloutAccessoryControlTapped: zu verwenden Delegate-Methode, die reagiert, wenn Benutzer auf das Steuerelement einer Callout-Ansicht tippen (sofern das Steuerelement ein Nachkomme von UIControl ist). In Ihrer Implementierung dieser Methode können Sie die Annotationsansicht der Callout-Ansicht ermitteln, so dass Sie wissen, welche Annotation der Benutzer angetippt hat. In einer Mac-App kann der View-Controller der Callout-Ansicht eine Aktionsmethode implementieren, die reagiert, wenn ein Benutzer in einer Callout-Ansicht auf das Steuerelement klickt.

    Wenn Sie eine benutzerdefinierte Ansicht anstelle eines Standard-Callouts verwenden, müssen Sie zusätzliche Arbeit leisten, um sicherzustellen, dass Ihre Callouts angezeigt und ausgeblendet werden, wenn Benutzer damit interagieren. Die folgenden Schritte beschreiben das Verfahren zur Herstellung einer benutzerdefinierten Callout erstellen, die eine Schaltfläche enthält:

    • Entwurf eine NSView oder UIView Unterklasse, die die benutzerdefinierte Callout darstellt. Es ist wahrscheinlich, dass die Unterklasse die Methode drawRect: implementieren muss, um Ihren benutzerdefinierten Inhalt zu zeichnen.

    • Erstellen Sie einen Ansichtscontroller, der die Callout-Ansicht initialisiert und die Aktion ausführt, die sich auf die Schaltfläche bezieht.

    • Implementieren Sie in der Anmerkungsansicht hitTest:, um auf Treffer zu reagieren, die außerhalb der Grenzen der Anmerkungsansicht liegen, jedoch innerhalb der Grenzen der Callout-Ansicht, wie in Listing 6-7 gezeigt.

    • Implementieren Sie in der Anmerkungsansicht setSelected:animated:, um Ihre Callout-Ansicht als Unteransicht der Anmerkungsansicht hinzuzufügen, wenn der Benutzer darauf klickt oder darauf klickt.

    • Wenn die Callout-Ansicht bereits sichtbar ist, wenn der Benutzer sie auswählt, sollte die Methode setSelected: die Callout-Unteransicht aus der Anmerkungsansicht entfernen (siehe Listing 6-8).

    • In dem initWithAnnotation: Methode des Anmerkungsansicht, stellen Sie die Eigenschaft auf canShowCalloutNO die Karte zu verhindern, dass von dem Standard-callout anzuzeigen, wenn der Benutzer der Annotation auswählt.

    Während es in Swift ist, https://github.com/robertmryan/CustomMapViewAnnotationCalloutSwift zeigt ein Beispiel, wie Sie die vollständige Anpassung des callout tun können (zÄndern Sie die Form der Callout-Blase, ändern Sie die Hintergrundfarbe usw.).

  4. Dieser vorherige Punkt skizziert eine ziemlich komplizierte Szenarien (d. H. Sie müssen Ihren eigenen Code zu erkennen Taps außerhalb der Ansicht zu schreiben, um es zu entlassen). Wenn Sie Unterstützung iOS 9, können Sie nur einen popover View-Controller verwenden, z.B .:

    - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation { 
        static NSString *identifier = @"MyAnnotationView"; 
    
        if ([annotation isKindOfClass:[MKUserLocation class]]) { 
         return nil; 
        } 
    
        MKPinAnnotationView *view = (id)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier]; 
        if (view) { 
         view.annotation = annotation; 
        } else { 
         view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier]; 
         view.canShowCallout = false; // note, we're not going to use the system callout 
         view.animatesDrop = true; 
        } 
    
        return view; 
    } 
    
    - (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view { 
        PopoverController *controller = [self.storyboard instantiateViewControllerWithIdentifier:@"AnnotationPopover"]; 
        controller.modalPresentationStyle = UIModalPresentationPopover; 
    
        controller.popoverPresentationController.sourceView = view; 
    
        // adjust sourceRect so it's centered over the annotation 
    
        CGRect sourceRect = CGRectZero; 
        sourceRect.origin.x += [mapView convertCoordinate:view.annotation.coordinate toPointToView:mapView].x - view.frame.origin.x; 
        sourceRect.size.height = view.frame.size.height; 
        controller.popoverPresentationController.sourceRect = sourceRect; 
    
        controller.annotation = view.annotation; 
    
        [self presentViewController:controller animated:TRUE completion:nil]; 
    
        [mapView deselectAnnotation:view.annotation animated:true]; // deselect the annotation so that when we dismiss the popover, the annotation won't still be selected 
    } 
    
+0

Rob vielen Dank, Sie wirklich retter sind. Ich freue mich, Ihre Antwort zu verbessern und zu akzeptieren :) – Yanchi

+1

Wenn ich nicht den Titel und den Untertitel in der "CustomAnnotation" -Klasse festlegen, wird Callout überhaupt nicht aufgerufen. Wie geht es weiter? – Satyam

+0

@Satyamsvv Wenn Sie 'canShowCallout' auf' NO' setzen, wird der Aufruf Ihrer 'didSelectAnnotationView' nicht durch das Vorhandensein oder Fehlen von' title'/'subtitle' beeinflusst. – Rob

1

ich einen anderen Ansatz aufgespult. Ich habe die anderen ausprobiert, aber sie schienen aufgebläht zu sein und ich wollte keine weiteren Klassen hinzufügen oder mich auf MKMapViewDelegate verlassen, um die Interaktion zu bewältigen.

I stattdessen überschreiben setSelected: animiert von meiner MKAnnotationView-Unterklasse. Der Trick besteht darin, die Grenzen der AnnotationView nach ihrer Auswahl zu erweitern, um die Aufrufansicht vollständig zu umfassen, und sie dann nach der Abwahl wieder auf normal zurückzusetzen. Dadurch können Ihre benutzerdefinierten Calls Berührungen und Interaktionen außerhalb der ursprünglichen Grenzen des MKAnnotationView akzeptieren. Hier

ist eine abgespeckte Codebeispiel für jeden Einstieg:

#define kAnnotationCalloutBoxTag 787801 
#define kAnnotationCalloutArrowTag 787802 
#define kAnnotationTempImageViewTag 787803 

-(void)setSelected:(BOOL)selected animated:(BOOL)animated 
{ 
    if (selected == self.selected) 
    { 
     NSLog(@"annotation already selected, abort!"); 
     return; 
    } 
    if (selected) 
    { 
     self.image = nil; //hide default image otherwise it takes the shape of the entire bounds 

     UIView* calloutBox = [self newCustomCallout]; 
     float imgW = [self unselectedSize].width; 
     float imgH = [self unselectedSize].height; 
     float arrowW = 20; 
     float arrowH = 12; 

     //Annotation frames wrap a center coordinate, in this instance we want the call out box to fall under the 
     //central coordinate, so we need to adjust the height to be double what the callout box height would be 
     //making the height *2, this is to make sure the callout view is inside of it. 
     self.bounds = CGRectMake(0, 0, calloutBox.frame.size.width, calloutBox.frame.size.height*2 + arrowH*2 + imgH); 
     CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); 

     UIView* imgView = [[UIImageView alloc] initWithImage:icon]; 
     [imgView setFrame:CGRectMake(center.x - imgW/2, center.y-imgH/2, imgW, imgH)]; 
     imgView.tag = kAnnotationTempImageViewTag; 
     [self addSubview:imgView]; 

     UIView* triangle = [self newTriangleViewWithFrame:CGRectMake(center.x-arrowW/2, center.y+imgH/2, arrowW, arrowH)]; 
     triangle.tag = kAnnotationCalloutArrowTag; 
     [self addSubview:triangle]; 

     [calloutBox setFrame:CGRectMake(0, center.y+imgH/2+arrowH, calloutBox.width, calloutBox.height)]; 
     calloutBox.tag = kAnnotationCalloutBoxTag; 
     [self addSubview:calloutBox]; 
    } 
    else 
    { 
     //return things back to normal 
     UIView* v = [self viewWithTag:kAnnotationCalloutBoxTag]; 
     [v removeFromSuperview]; 
     v = [self viewWithTag:kAnnotationCalloutArrowTag]; 
     [v removeFromSuperview]; 
     v = [self viewWithTag:kAnnotationTempImageViewTag]; 
     [v removeFromSuperview]; 

     self.image = icon; 
     self.bounds = CGRectMake(0, 0, [self unselectedSize].width, [self unselectedSize].height); 
    } 
    [super setSelected:selected animated:animated]; 
} 

-(CGSize)unselectedSize 
{ 
    return CGSizeMake(20,20); 
} 

-(UIView*)newCustomCallout 
{ 
    //create your own custom call out view 
    UIView* v = [[UIView alloc] initWithFrame:CGRectMake(0,0,250,250)]; 
    v.backgroundColor = [UIColor greenColor]; 
    return v; 
} 

-(UIView*)newTriangleWithFrame:(CGRect)frame 
{ 
    //create your own triangle 
    UIImageView* v = [[UIImageView alloc] initWithFrame:frame]; 
    [v setImage:[UIImage imageNamed:@"trianglePointedUp.png"]]; 
    return v; 
} 
0
(void)mapView:(MKMapView *)mapViewIn didSelectAnnotationView:(MKAnnotationView *)view { 
    if(![view.annotation isKindOfClass:[MKUserLocation class]]) 
    { 
     CustomeCalloutViewController *calloutView = [[CustomeCalloutViewController alloc]initWithNibName:@"CustomeCalloutViewController" bundle:nil]; 
     [calloutView setPopinTransitionStyle:BKTPopinTransitionStyleSlide]; 
     [calloutView setPopinTransitionDirection:BKTPopinTransitionDirectionTop]; 
     [self presentPopinController:calloutView animated:YES completion:^{ 
      NSLog(@"Popin presented !"); 
     }]; 
     [mapView deselectAnnotation:view.annotation animated:true]; 
    } 
}