12

I'm currently creating and returning a custom view with the google maps ios SDK by setting delegate to self and using the following code.

#pragma mark - GMSMapViewDelegate
-(UIView*)mapView:(GMSMapView *)mapView markerInfoWindow:(id<GMSMarker>)marker {
    int popupWidth = 200;
    int contentWidth = 180;
    int contentPad = 10;
    int popupHeight = 140;
    int popupBottomPadding = 16;
    int popupContentHeight = popupHeight - popupBottomPadding;
    int buttonHeight = 30;

    UIView *outerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, popupWidth, popupHeight)];

    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, popupWidth, popupContentHeight)];
    [view setBackgroundColor:[UIColor whiteColor]];

    UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(contentPad, 0, contentWidth, 22)];
    [titleLabel setFont:[UIFont systemFontOfSize:17.0]];
    titleLabel.text = [marker title];

    UILabel *descriptionLabel = [[UILabel alloc] initWithFrame:CGRectMake(contentPad, 24, contentWidth, 20)];
    [descriptionLabel setFont:[UIFont systemFontOfSize:11.0]];
    descriptionLabel.text = [marker snippet];

    [view addSubview:titleLabel];
    [view addSubview:descriptionLabel];

    UIButton *directionButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    directionButton.frame = CGRectMake(contentPad, 45, contentWidth, buttonHeight);
    [directionButton setTitle:@"Directions" forState:UIControlStateNormal];
    [directionButton addTarget:self action:@selector(directionsPressed) forControlEvents:UIControlEventTouchDown];

    UIButton *viewLocationButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [viewLocationButton addTarget:self action:@selector(viewLocationPressed) forControlEvents:UIControlEventTouchUpInside];
    [viewLocationButton setTitle:@"View Location" forState:UIControlStateNormal];
    viewLocationButton.frame = CGRectMake(contentPad, 80, contentWidth, buttonHeight);

    // handle bottom dealio
    UIImage *bottomImage = [UIImage imageNamed:@"map-pointer-bottom"];
    UIImageView *bottomView = [[UIImageView alloc] initWithFrame:CGRectMake((popupWidth / 2) - (bottomImage.size.width / 2), (popupContentHeight), bottomImage.size.width, bottomImage.size.height)];
    [bottomView setImage:bottomImage];

    [outerView addSubview:view];
    [outerView addSubview:bottomView];
    [outerView addSubview:directionButton];
    [outerView addSubview:viewLocationButton];

    ListItem *li = (ListItem*)[marker userData];
    self.currentItem = li;
    NSLog(@"List Item %@ - %@", li.type, li.typeid);

    return outerView;
}

-(void)directionsPressed {
    NSLog(@"Directions Pressed");
}

-(void)viewLocationPressed {
    NSLog(@"Location View Pressed");
}

- (void)mapView:(GMSMapView *)mapView didTapInfoWindowOfMarker:(id<GMSMarker>)marker {
    NSLog(@"Tap Captured");
}

The didTapWindowOfMarker is being fired when i tap the custom view, but neither of the target methods for the buttons are being fired.

Any ideas for why this might be?

ercmcd
  • 143
  • 1
  • 1
  • 9

5 Answers5

24

Possibly, as mentioned officially in documentation of Google Maps Android API, the below restriction regarding infowindows applies to Google Maps iOS SDK also :

Info window is not a live View, rather the view is rendered as an image onto the map. As a result, any listeners you set on the view are disregarded and you cannot distinguish between click events on various parts of the view. You are advised not to place interactive components — such as buttons, checkboxes, or text inputs — within your custom info window.

So basically clicking on any part of the infowindow will trigger only "didTapWindowOfMarker"

Toseef Khilji
  • 17,192
  • 12
  • 80
  • 121
tony m
  • 4,769
  • 1
  • 21
  • 28
  • 3
    I found the above to be the case and instead opted to implement -(BOOL)mapView:(GMSMapView*)mapView didTapMarker:(id)marker; and return YES; within that method such that the response for selecting a marker did not use the info window at all but instead used my own solution. Thanks for the extra info! – ercmcd Mar 11 '13 at 14:15
  • 2
    and you move your solution when the map move? – Luca Torella Jun 13 '13 at 22:49
  • 2
    @ercmcd can you please post your solution as I'm struggling with the same issue – liv a Aug 07 '13 at 19:36
10

Swift 3.0 Solution

 //empty the default infowindow
        func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
            return UIView()
        }

        // reset custom infowindow whenever marker is tapped
        func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {

            customInfoView.removeFromSuperview()
        //    customInfoView.button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
            self.view.addSubview(customInfoView)

            // Remember to return false
            // so marker event is still handled by delegate
            return false
        }

        // let the custom infowindow follows the camera
        func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) {
            if (locationMarker != nil){
                let location = locationMarker.position
                customInfoView.center = mapView.projection.point(for: location)
            }
        }

        // take care of the close event
        func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) {
            customInfoView.removeFromSuperview()
        }

I have added customInfoWindow like this

and make outlet of this view(customInfoWindow) in same controller which has mapView.

I got the idea from this link thanks to this developer Custom and interactive googlemaps(IOS SDK) infowindow

Sourabh Sharma
  • 8,222
  • 5
  • 68
  • 78
2

I have a UView and I'm adding a delegate to the view so that a UIButton calls a selector. For the google map, I don't do anything when I call

- (void) mapView:(GMSMapView *)mapView didTapInfoWindowOfMarker:(GMSMarker *)marker;

However, I set my infowindow (my custom view) delegate to self, and call all my actions when I press the button that's linked to the selector.

kevinl
  • 4,194
  • 6
  • 37
  • 55
  • 2
    Hi, is the button action then triggered for a tap anywhere in the custom infoWindow or just a UI Button inside it? If just the UIButton, then could you please paste the code. – vkinra Mar 13 '14 at 00:26
1

UPDATE:

Here is the code I use in order to detect tap on buttons added to my infoWindows. I create a custom infoWindow with 2 fake buttons (they could actually replaced by images because they won't trigger any action) and I add a completely transparent overlay with 2 real buttons over the infoWindow. These buttons will trigger the actions.

And I use a few delegate methods or KVC in order to move the overlay when the infoWindow itself is moved.

- (UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker {
    [self.actionOverlayCalloutView removeFromSuperview];
    UIView *calloutView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, infoWindowWidth, infoWindowHeight)];

    float offset = anchorSize * M_SQRT2;
    CGAffineTransform rotateBy45Degrees = CGAffineTransformMakeRotation(M_PI_4);
    UIView *arrow = [[UIView alloc] initWithFrame:CGRectMake((infoWindowWidth - anchorSize)/2.0, infoWindowHeight - offset, anchorSize, anchorSize)];
    arrow.transform = rotateBy45Degrees;
    arrow.backgroundColor = [UIColor lightGrayColor];
    [calloutView addSubview:arrow];

    UIView *contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, infoWindowWidth, infoWindowHeight - offset/2)];
    [contentView setBackgroundColor:[UIColor whiteColor]];

    contentView.layer.cornerRadius = 5;
    contentView.layer.masksToBounds = YES;

    contentView.layer.borderColor = [UIColor lightGrayColor].CGColor;
    contentView.layer.borderWidth = 1.0f;

    self.actionOverlayCalloutView =
    [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:contentView]]; //hack to copy a view...
    self.actionOverlayCalloutView.backgroundColor = [UIColor lightGrayColorWithAlpha:0.5];
    self.actionOverlayCalloutView.layer.cornerRadius = 5;
    NSMutableArray *falseButtons = [NSMutableArray array];
    NSMutableArray *actionButtons = [NSMutableArray array];
    PointMapItem *pointAnnotation = marker.userData;
    if ([pointAnnotation canPerformSend]) {
        UIButton *button = [[UIButton alloc] init];
        [button setImage:[UIImage imageNamed:@"imageButton1.png"] forState:UIControlStateNormal];
        [falseButtons addObject:button];
        UIButton *activableButton = [[UIButton alloc] init];
        [activableButton addTarget:self action:@selector(onButton1Clicked) forControlEvents:UIControlEventTouchUpInside];
        [actionButtons addObject:activableButton];
    }
    if ([pointAnnotation canPerformShowDetails]) {
        UIButton *button = [[UIButton alloc] init];
        [button setImage:[UIImage imageNamed:@"imageButton1.png"] forState:UIControlStateNormal];
        [falseButtons addObject:button];
        UIButton *activableButton = [[UIButton alloc] init];
        [activableButton addTarget:self action:@selector(onButton2Clicked) forControlEvents:UIControlEventTouchUpInside];
        [actionButtons addObject:activableButton];
    }
    int buttonWidth = contentView.frame.size.width / [falseButtons count];
    int currentOffset = 0;
    for (int i=0; i<falseButtons.count; i++) {
        UIButton *falseButton = [falseButtons objectAtIndex:i];
        UIButton *activableButton = [actionButtons objectAtIndex:i];
        [falseButton setFrame:CGRectMake(currentOffset, 0, buttonWidth, contentView.frame.size.height)];
        currentOffset += buttonWidth;
        activableButton.frame = falseButton.frame;
        [activableButton setTitle:@"" forState:UIControlStateNormal];
        [self.actionOverlayCalloutView addSubview:activableButton];
        [contentView addSubview:falseButton];
    }
    [calloutView addSubview:contentView];

    CLLocationCoordinate2D anchor = [self.mapView.selectedMarker position];
    CGPoint point = [self.mapView.projection pointForCoordinate:anchor];
    point.y -= self.mapView.selectedMarker.icon.size.height + offset/2 + (infoWindowHeight - offset/2)/2;
    self.actionOverlayCalloutView.center = point;

    [self.mapView addSubview:self.actionOverlayCalloutView];
    return calloutView;
}

- (void)mapView:(GMSMapView *)pMapView didChangeCameraPosition:(GMSCameraPosition *)position {
    if (pMapView.selectedMarker != nil && self.actionOverlayCalloutView.superview) {
        CLLocationCoordinate2D anchor = [self.mapView.selectedMarker position];
        CGPoint point = [self.mapView.projection pointForCoordinate:anchor];
        float offset = anchorSize * M_SQRT2;
        point.y -= self.mapView.selectedMarker.icon.size.height + offset/2 + (infoWindowHeight - offset/2)/2;
        self.actionOverlayCalloutView.center = point;
    } else {
        [self.actionOverlayCalloutView removeFromSuperview];
    }
}

- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate {
    [self.actionOverlayCalloutView removeFromSuperview];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"mapView.selectedMarker"]) {
        if (!self.mapView.selectedMarker) {
            [self.actionOverlayCalloutView removeFromSuperview];
        }
    }
}

- (void)onButton2Clicked {
    //your code
    self.mapView.selectedMarker = nil;
}

- (void)onButton1Clicked {
    // your code;
    self.mapView.selectedMarker = nil;
}

FORMER POST:

Have a look at this thread and specifically at #9, should be helpful https://code.google.com/p/gmaps-api-issues/issues/detail?id=4961

Aurelien Porte
  • 2,692
  • 27
  • 32
  • While this link may answer the question, it is better to include the essential parts of the answer [here](http://meta.stackexchange.com/a/8259) and provide the link for reference. Link-only answers can become invalid if the linked page changes. – bummi Nov 29 '13 at 18:13
  • Wouldn't it be just as effective and less complicated to display a real user interface on top, rather than separating the looks from the action and having to keep them in sync? – Agent Friday Feb 08 '20 at 23:27
-2

1)Create one subview which you want to show in infoWindow.

2)Set frame of subview equals to frame of infoWindow view.

  [subView setFrame:infoview.frame];
  subView =  [[[NSBundle mainBundle] loadNibNamed:@"viewName" owner:self options:nil] objectAtIndex:0];
  [self.mapview addSubview:subView];
Dhananjay Patil
  • 442
  • 3
  • 10