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.
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.