Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Way to customize individual markers added to cluster manager? #21

Closed
s-p-a-r-k opened this issue Jul 27, 2016 · 31 comments

Comments

Projects
None yet
@s-p-a-r-k
Copy link

commented Jul 27, 2016

Is there a way to change icon and title of individual markers when they are not clustered? For Android, I know I can extend DefaultClusterRenderer and override onBeforeClusterItemRendered method, but I cannot find a way to do this for iOS

@mountainvat

This comment has been minimized.

Copy link
Contributor

commented Jul 28, 2016

What you can do is extend GMUDefaultClusterRenderer and override this method

- (GMSMarker *)markerWithPosition:(CLLocationCoordinate2D)position
                             from:(CLLocationCoordinate2D)from
                         userData:(id)userData
                      clusterIcon:(UIImage *)clusterIcon
                         animated:(BOOL)animated

This is where the marker is actually created. I've included some sample code here. Please note the new code is between the "// new code" markers. Let us know how you go. We will plan to make a better API for this use case in a later release.

  CLLocationCoordinate2D initialPosition = animated ? from : position;
  GMSMarker *marker = [GMSMarker markerWithPosition:initialPosition];
  marker.userData = userData;
  if (clusterIcon != nil) {
    marker.icon = clusterIcon;
    marker.groundAnchor = CGPointMake(0.5, 0.5);
  }
 // new code starts here.
  else {
   // this is new code for customizing individual markers
    marker.icon =  [self getCustomIconForItem:userData];  // stub method
    marker.title = [self getCustomTitleItem:userData];  // stub method
  }
// new code ends here.

  marker.map = _mapView;

  if (animated) {
    [CATransaction begin];
    [CATransaction setAnimationDuration:kGMUAnimationDuration];
    marker.layer.latitude = position.latitude;
    marker.layer.longitude = position.longitude;
    [CATransaction commit];
  }
@s-p-a-r-k

This comment has been minimized.

Copy link
Author

commented Aug 1, 2016

@mountainvat This works for changing all of individual markers to the same icon and title, but I do not know how to use this code to change only some of individual markers. I would like to set the icon and the title of markers depending on a variable stored in my custom GMUClusterItem. Is there a way to do this too?

@mountainvat

This comment has been minimized.

Copy link
Contributor

commented Aug 2, 2016

one way you can do replace this code:

    marker.icon =  [self getCustomIconForItem:userData];  // stub method
    marker.title = [self getCustomTitleItem:userData];  // stub method

with

    marker.icon =  [self getCustomIconForItem:userData] ? :  marker.icon;  // stub method
    marker.title = [self getCustomTitleItem:userData] ? : marker.title;  // stub method

and in the getCustomXXX method, you'd return nil if the item should not be customized.

Hope it helps.

@s-p-a-r-k

This comment has been minimized.

Copy link
Author

commented Aug 2, 2016

@mountainvat I am sorry, but I'm not familiar with Objective C and don't know how I should replace [self getCustomIconForItem:userData] and [self getCustomTitleItem:userData].
If I want to title my marker with POIItem's name (from https://developers.google.com/maps/documentation/ios-sdk/utility/marker-clustering), for example, should I call userData's name variable?
`

/// Point of Interest Item which implements the GMUClusterItem protocol.
class POIItem: NSObject, GMUClusterItem {
    var position: CLLocationCoordinate2D
    var name: String!

    init(position: CLLocationCoordinate2D, name: String) {
        self.position = position
        self.name = name
      }
    }`
@mountainvat

This comment has been minimized.

Copy link
Contributor

commented Aug 3, 2016

You can safely cast userData to your cluster item object. So I'd imagine something like this:

- (NSString *)getCustomTitleItem:(id)userData {
  POIItem *item = userData;
  return item.name;
}
@s-p-a-r-k

This comment has been minimized.

Copy link
Author

commented Aug 3, 2016

@mountainvat I tried to put your code in GMSDefaultClusterRenderer.m, but it says use of undeclared identifier. Should I move POIItem class to Clustering folder that has all Google-Maps-IOS-Utils classes?

@mountainvat

This comment has been minimized.

Copy link
Contributor

commented Aug 4, 2016

I would not put the code inside GMSDefaultClusterRenderer.m. I'd rather extend it in your own application's classes. Since the custom renderer lives in your app's target it should have access yo your GMUClusterItem classes. So you could do something like this:

@interface MyClusterRenderer : GMSDefaultClusterRenderer
@end

@implementation MyClusterRenderer

- (NSString *)getCustomTitleItem:(id)userData {
  YourOwnGMUClusterItemClass *item = userData;
  return item.name;
}

@end
@s-p-a-r-k

This comment has been minimized.

Copy link
Author

commented Aug 5, 2016

@mountainvat That makes sense. Apparently, I did not read your very first answer that said to extend GMSDefaultClusterRenderer. However, it fails to build. ("Command failed due to signal: Segmentation fault: 11"
This is what I have for my custom renderer:

#import <Foundation/Foundation.h>
#import "CustomRenderer.h"
#import "MyProjectName-swift.h"

// Animation duration for marker splitting/merging effects.
static const double kGMUAnimationDuration = 0.5;  // seconds.

@implementation CustomRenderer
// Map view to render clusters on.
__weak GMSMapView *_mapView;

- (NSString *)getCustomTitleItem:(id)userData {
    MyOwnClusterItemClass *item = userData;
    return item.title;
}

- (GMSMarker *)markerWithPosition:(CLLocationCoordinate2D)position
                             from:(CLLocationCoordinate2D)from
                         userData:(id)userData
                      clusterIcon:(UIImage *)clusterIcon
                         animated:(BOOL)animated {
    CLLocationCoordinate2D initialPosition = animated ? from : position;
    GMSMarker *marker = [GMSMarker markerWithPosition:initialPosition];
    marker.userData = userData;
    if (clusterIcon != nil) {
        marker.icon = clusterIcon;
        marker.groundAnchor = CGPointMake(0.5, 0.5);
    } else {
        marker.title = [self getCustomTitleItem:userData] ? : marker.title;
    }
    marker.map = _mapView;

    if (animated) {
        [CATransaction begin];
        [CATransaction setAnimationDuration:kGMUAnimationDuration];
        marker.layer.latitude = position.latitude;
        marker.layer.longitude = position.longitude;
        [CATransaction commit];
    }
    return marker;
}

@end 

Do you know what is causing this error?

@mountainvat

This comment has been minimized.

Copy link
Contributor

commented Aug 9, 2016

I cannot reason why it would cause a Segmentation fault. Do you have a stacktrace?

Anyway I can see a problem with your code (maybe not related): the _mapView instance var is always nil. What you also need to do is override initWithMapView to capture the mapView into your private _mapView variable.

Add this method to your new derived class:


- (instancetype)initWithMapView:(GMSMapView *)mapView
           clusterIconGenerator:(id<GMUClusterIconGenerator>)iconGenerator {
  self = [super initWithMapView:mapView clusterIconGenerator:iconGenerator];
  if (self) {
    _mapView = mapView;
  }
}
@mountainvat

This comment has been minimized.

Copy link
Contributor

commented Aug 23, 2016

@s-p-a-r-k : Any luck with this? Can we close the ticket?

@s-p-a-r-k

This comment has been minimized.

Copy link
Author

commented Aug 24, 2016

@mountainvat Sorry, I haven't had a chance to try it. But you can close it. I will tell you if it works. Thank you very much for your help!

@vjerryv

This comment has been minimized.

Copy link

commented Sep 22, 2016

@mountainvat I've tried to override GMUDefaultClusterRenderer, but I got this error.

Duplicate definition of category 'GoogleMaps' on interface 'GMSCoordinateBounds'

screenshot 2016-09-22 11 40 37

@astrolope

This comment has been minimized.

Copy link

commented Sep 28, 2016

I must be numb-skulled, I'm trying to over-ride GMUDefaultClusterRenderer to achieve more dynamicity in my custom markers without having to edit the main file. I copied the code from the original file to a new class as previously aforementioned in the previous post. Although I'm now receiving mach-o linker errors after setting it up. Deleting my custom class completely removes the errors. I've managed to change the marker fine, but am struggling on how to do it "the right way".

@mountainvat

This comment has been minimized.

Copy link
Contributor

commented Sep 29, 2016

FYI: we are working on making marker customization easier. Will have a release out end of this week or earlier next week. So stay tuned.

@unlimitedK

This comment has been minimized.

Copy link

commented Nov 26, 2016

I have gone through the new GMUClusterRendererDelegate

How can I use this in swift, I have to show markerimage(which would again be changing to different conditions) and have a label at the bottom of this marker. In Swift, I have used func renderer(renderer: GMUClusterRenderer, markerForObject object: AnyObject) -> GMSMarker

but it is not getting called before placing the marker so that I can change images on that?

@mountainvat

This comment has been minimized.

Copy link
Contributor

commented Nov 27, 2016

If you update to version 1.1.x there should be new events for before and after the markers are added to map. Please see this post for more information: https://github.com/googlemaps/google-maps-ios-utils/blob/master/CustomMarkers.md

@kwstasna

This comment has been minimized.

Copy link

commented Nov 29, 2016

@mountainvat could you provide a swift example for this GMUDefaultClusterRenderer delegate?

@jamesynotnot

This comment has been minimized.

Copy link

commented Nov 29, 2016

@kwstasna

This comment has been minimized.

Copy link

commented Nov 29, 2016

@jamesynotnot actually wanted to translate somehow this file

https://github.com/googlemaps/google-maps-ios-utils/blob/master/app/CustomMarkerViewController.m

Which has the functions to make this view happen.
Because im in the same position to have a json with names and images urls.

@jamesynotnot

This comment has been minimized.

Copy link

commented Nov 29, 2016

@kwstasna

This comment has been minimized.

Copy link

commented Nov 29, 2016

@jamesynotnot not the best way 😞

@jamesynotnot

This comment has been minimized.

Copy link

commented Dec 2, 2016

@hansemannn

This comment has been minimized.

Copy link
Contributor

commented Dec 29, 2016

Hey guys, just came across this during my PR to support clustering with google maps in Titanium (here). A convenient way would be to just subclass it and use the protocol to create everything. Unfortunately, methods like markerWithPosition:from:userData:clusterIcon:animated: are not in the public interface, so they won't be suggested. That would be one of the improvements.

Additionally to that, things links handling the mapView reference should be solved using a reference to the super class, so we don't need to declare own initializers. Not sure if I can help here, but this sounds like a doable change! :-)

@BeetLab

This comment has been minimized.

Copy link

commented Jan 10, 2017

@mountainvat - But what about that :

/**

  • Raised when a marker (for a cluster or an item) is about to be added to the map.
  • Use the marker.userData property to check whether it is a cluster marker or an
  • item marker.
    */
  • (void)renderer:(id)renderer willRenderMarker:(GMSMarker *)marker;

in @protocol GMUClusterRendererDelegate?

@mountainvat

This comment has been minimized.

Copy link
Contributor

commented Jan 10, 2017

@BeetLab you can use the event to customize your mark before it's added to the map. See https://github.com/googlemaps/google-maps-ios-utils/blob/master/CustomMarkers.md for an example of how to use it.

@prsreejith

This comment has been minimized.

Copy link

commented Feb 12, 2017

How to load this cluster GMSMapview into custom view like below ?

  • (void)loadView {
    GMSCameraPosition *camera =
    [GMSCameraPosition cameraWithLatitude:kCameraLatitude longitude:kCameraLongitude zoom:10];
    _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
    self.myMapView = _mapView;
    }

currently its loading only to self.view

@grundmanise

This comment has been minimized.

Copy link

commented May 1, 2017

@mountainvat Hello,

in this function:

func renderer(_ renderer: GMUClusterRenderer, willRenderMarker marker: GMSMarker) {}

makrer.iconView is always nil even if I set it up before like this:

func renderer(_ renderer: GMUClusterRenderer, willRenderMarker marker: GMSMarker) {
      
      // Check if marker or cluster
      if marker.userData is PlaceMarker {

          if let userData = marker.userData as? PlaceMarker {
             marker.iconView = MarkerView(caption: userData.caption)
          }

          marker.groundAnchor = CGPoint(x: 0.5, y: 1)
          marker.isFlat = true
          marker.appearAnimation = kGMSMarkerAnimationPop

      } else {
          // Apply custom view for cluster
          marker.iconView = ClusterViewIcon()
          
          // Show clusters above markers
          marker.zIndex = 1000;
          marker.groundAnchor = CGPoint(x: 0.5, y: 1)
          marker.isFlat = true
          marker.appearAnimation = kGMSMarkerAnimationPop
      }
  }

how can one implement a guard to only setup iconView and other marker properties only when the marker is rendered for the first time? otherwise it is just a waste of resources.. (and also the animation happens on every zoom level change)

@mountainvat

This comment has been minimized.

Copy link
Contributor

commented May 1, 2017

How many different cluster views do you have? If you have only 1 custom view (shared by many clusters) then I'd suggest return the same instance in ClusterViewIcon() so that the view is only rendered once.

In addition, this event is only raised once when the marker is brought into view unless a recluster happens (say on a zoom level change). For this maybe raise a separate issue and we will see if we can address it in the coming release.

@grundmanise

This comment has been minimized.

Copy link

commented May 1, 2017

I did it before - stored a pre-created view in a var, and assigned this var to the iconView, thus the view was rendered only once. But now I need to setup captions. Each marker has it's own caption. So I can't use 1 view for every marker.

Exactly. On the zoom level change. If nothing changes (i.e. no clustering / declustering happens), we recreate already rendered markers and waste resources.

@miragessee

This comment has been minimized.

Copy link

commented Jan 11, 2018

@jamesynotnot

If don't call func renderer:

implementation GMUClusterRendererDelegate

renderer.delegate = self

@Prathapreddy26

This comment has been minimized.

Copy link

commented Jan 8, 2019

@mountainvat Can you help with my problem? I am done with the clustering. The markers are clustered. but, it is not showing the Cluster count. Any help will be appreciable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.