3

I created an iOS application based on maps in which I thought to use Google Maps SDK for iOS instead of Mapkit, I found the documentation but it I didn’t find methods related to custom annotation view, Can anyone provide me the solution for how to create custom annotation view(info window) and how to add content(title, snippet) for it.

smily
  • 357
  • 1
  • 7
  • 18

2 Answers2

13

I don't know about y'all, but I find Google's rendered UIView info windows to be a bit restricting. Using SMCalloutView and Ryan Maxwell's example project, it's possible to present more interactive views.

This works on Google Maps SDK v1.8.1, as of 2014-June-10.

Default SMCalloutView on Google Maps

First, do some set up:

#import <SMCalloutView/SMCalloutView.h>

static const CGFloat CalloutYOffset = 10.0f;

@interface ViewController ()
@property (strong, nonatomic) SMCalloutView *calloutView;
@property (strong, nonatomic) UIView *emptyCalloutView;
@end

Initialize SMCalloutView, add a button to it, then create an empty UIView:

- (void)viewDidLoad
{
    /* all your other view init, settings, etc... */

    self.calloutView = [[SMCalloutView alloc] init];
    self.calloutView.hidden = YES;

    UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
    [button addTarget:self
               action:@selector(calloutAccessoryButtonTapped:)
     forControlEvents:UIControlEventTouchUpInside];
    self.calloutView.rightAccessoryView = button;

    self.emptyCalloutView = [[UIView alloc] initWithFrame:CGRectZero];
}

We have to draw that empty UIView to satisfy the Maps SDK, but the view we will display is SMCalloutView. I also set a reuseable vertical offset for the callout view.

Add delegate methods to handle info window calls:

#pragma mark - GMSMapViewDelegate

- (UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker {
    CLLocationCoordinate2D anchor = marker.position;

    CGPoint point = [mapView.projection pointForCoordinate:anchor];

    self.calloutView.title = marker.title;

    self.calloutView.calloutOffset = CGPointMake(0, -CalloutYOffset);

    self.calloutView.hidden = NO;

    CGRect calloutRect = CGRectZero;
    calloutRect.origin = point;
    calloutRect.size = CGSizeZero;

    [self.calloutView presentCalloutFromRect:calloutRect
                                      inView:mapView
                           constrainedToView:mapView
                                    animated:YES];

    return self.emptyCalloutView;
}

- (void)mapView:(GMSMapView *)pMapView didChangeCameraPosition:(GMSCameraPosition *)position {
    /* move callout with map drag */
    if (pMapView.selectedMarker != nil && !self.calloutView.hidden) {
        CLLocationCoordinate2D anchor = [pMapView.selectedMarker position];

        CGPoint arrowPt = self.calloutView.backgroundView.arrowPoint;

        CGPoint pt = [pMapView.projection pointForCoordinate:anchor];
        pt.x -= arrowPt.x;
        pt.y -= arrowPt.y + CalloutYOffset;

        self.calloutView.frame = (CGRect) {.origin = pt, .size = self.calloutView.frame.size };
    } else {
        self.calloutView.hidden = YES;
    }
}

- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate {
    self.calloutView.hidden = YES;
}

- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker {
    /* don't move map camera to center marker on tap */
    mapView.selectedMarker = marker;
    return YES;
}

Handle touches on the callout button, here using an alert view with marker title and snippet:

- (void)calloutAccessoryButtonTapped:(id)sender {
    if (mapView_.selectedMarker) {
        GMSMarker *marker = mapView_.selectedMarker;
        //NSDictionary *userData = marker.userData;

        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:marker.title
                                                            message:marker.snippet
                                                           delegate:nil
                                                  cancelButtonTitle:@"OK"
                                                  otherButtonTitles:nil];
        [alertView show];
    }
}

Obviously, make sure your ViewController(.h) listens to GMSMapViewDelegate:

@interface ViewController : UIViewController <GMSMapViewDelegate>

And that should basically work. For a complete xcode project, see the aforementioned example from Ryan Maxwell.

friedbunny
  • 2,421
  • 1
  • 23
  • 38
  • 1
    Need to also include this line: self.calloutView = [[SMCalloutView alloc] init]; and also: self.emptyCalloutView = [[UIView alloc] initWithFrame:CGRectZero]; in the viewDidLoad() – Zhang Oct 09 '14 at 10:10
  • Oops, I went back and added `viewDidLoad` initialization. Thanks for catching that, @Zhang! – friedbunny Oct 09 '14 at 20:23
  • 1
    Probably be nice to also let others know that if you want to add tap on whole callout view, there's a protocol delegate method you can simply implement, -(void)calloutViewClicked:(SMCalloutView *)calloutView the protocol to conform to is: SMCalloutViewDelegate – Zhang Oct 10 '14 at 02:05
  • @friedbunny I understand this post is over 2 years old, but is there any word on this being developed for swift? I'm struggling to find something. – tryingtolearn Jul 26 '16 at 22:53
10

If you check GMSMapView.h within GoogleMaps.Framework, you can see the below method which should let you add a custom infowindow for a marker, instead of using standard title and snippet alone:

NOTE you (op) say annotationView=infoWindow
BUT NORMAL: annotationView = marker itself and calloutView = infoWindow

/**
 * Called when a marker is about to become selected, and provides an optional
 * custom info window to use for that marker if this method returns a UIView.
 * If you change this view after this method is called, those changes will not
 * necessarily be reflected in the rendered version.
 *
 * The returned UIView must not have bounds greater than 500 points on either
 * dimension.  As there is only one info window shown at any time, the returned
 * view may be reused between other info windows.
 *
 * @return The custom info window for the specified marker, or nil for default
 */
- (UIView *)mapView:(GMSMapView *)mapView
    markerInfoWindow:(id<GMSMarker>)marker;
Daij-Djan
  • 49,552
  • 17
  • 113
  • 135
tony m
  • 4,769
  • 1
  • 21
  • 28
  • an annotation view is not shown on click but always. this is more a callout view – Daij-Djan Mar 22 '13 at 16:55
  • Maybe I misunderstand you, @Daij-Djan, but what Apple appears to call an ["annotation view"](https://developer.apple.com/library/ios/documentation/userexperience/conceptual/LocationAwarenessPG/AnnotatingMaps/AnnotatingMaps.html), Google calls an ["info window"](https://developers.google.com/maps/documentation/ios/reference/protocol_g_m_s_map_view_delegate-p#a961e81669891377c9ebee752ce53340f). – friedbunny Jun 10 '14 at 04:30
  • no the don't - but the op did :) NORMAL: annotationView = marker itself and calloutView = infoWindow BUT the op mixed it up – Daij-Djan Jun 10 '14 at 11:13