Marker icon image duplication - ios

I have two images: small for default marker state and big for selected.
After tapping on marker, I'm changing marker icons for new state, but sometimes I see that small icon is placed over big icon. I thought it should be updated with tracksViewChanges property, but it doesn't.
You can see green marker on image, which have big icon under small.
My code is simple:
-(BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker
{
if (self.lastMarker) {
Car *lastCar = (Car*)self.lastMarker.userData;
self.lastMarker.icon = [lastCar.provider smallPinIcon];
}
Car *car = (Car*)marker.userData;
marker.tracksViewChanges = YES;
marker.icon = [car.provider bigPinIcon];
[UIView animateWithDuration:0.01 animations:^{
}
completion:^(BOOL finished) {
marker.tracksViewChanges = NO;
}];
self.lastMarker = marker;
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] init];
bounds = [bounds includingCoordinate:self.userMarker.position];
bounds = [bounds includingCoordinate:marker.position];
GMSCameraUpdate *update = [GMSCameraUpdate fitBounds:bounds withEdgeInsets:UIEdgeInsetsMake(120, 60, 120, 60)];
[self.mapView animateWithCameraUpdate:update];
return YES;
}
Methods smallPinIcon and bigPinIcon caching actual images.
This is Google Maps SDK bug or I'm doing something wrong?

Related

iOS Google Maps, different custom images for Clusters and individual markers

I am using Google Maps SDK in my iOS app. I am populating the map using the clustering methods.
I have set custom images for the different clustering buckets ex. 10,20...
The individual markers however have the default (google maps red marker icon).
I would like a custom icon for clustering and a different one for single markers.
However inside the methods that render the Cluster that add markers, if you set the marker icons it changes all of the images not just singles.
How do I set different icons for singles and clusters?
this adds the items to clusterManager
id<GMUClusterItem> item =
[[POIItem alloc] initWithPosition:CLLocationCoordinate2DMake([bay.latitude doubleValue], [bay.longitude doubleValue]) name:bay.name status:bay.marker_status];
[clusterManager addItem:item];
Here I add the icons for the cluster buckets
- (id<GMUClusterIconGenerator>)iconGeneratorWithImages {
return [[GMUDefaultClusterIconGenerator alloc] initWithBuckets:#[ #10, #50, #100, #200, #1000 ]
backgroundImages:#[
[UIImage imageNamed:#"big_parking_pin_img"],
[UIImage imageNamed:#"big_parking_pin_img"],
[UIImage imageNamed:#"big_parking_pin_img"],
[UIImage imageNamed:#"big_parking_pin_img"],
[UIImage imageNamed:#"big_parking_pin_img"]
]];
}
This is where the google cluster class adds markers
- (void)renderCluster:(id<GMUCluster>)cluster animated:(BOOL)animated {
float zoom = _mapView.camera.zoom;
if ([self shouldRenderAsCluster:cluster atZoom:zoom]) {
CLLocationCoordinate2D fromPosition;
if (animated) {
id<GMUCluster> fromCluster =
[self overlappingClusterForCluster:cluster itemMap:_itemToOldClusterMap];
animated = fromCluster != nil;
fromPosition = fromCluster.position;
}
UIImage *icon = [_clusterIconGenerator iconForSize:cluster.count];
GMSMarker *marker = [self markerWithPosition:cluster.position
from:fromPosition
userData:cluster
clusterIcon:icon
animated:animated];
[_markers addObject:marker];
} else {
for (id<GMUClusterItem> item in cluster.items) {
CLLocationCoordinate2D fromPosition;
BOOL shouldAnimate = animated;
if (shouldAnimate) {
GMUWrappingDictionaryKey *key = [[GMUWrappingDictionaryKey alloc] initWithObject:item];
id<GMUCluster> fromCluster = [_itemToOldClusterMap objectForKey:key];
shouldAnimate = fromCluster != nil;
fromPosition = fromCluster.position;
}
GMSMarker *marker = [self markerWithPosition:item.position
from:fromPosition
userData:item
clusterIcon:nil
animated:shouldAnimate];
[_markers addObject:marker];
[_renderedClusterItems addObject:item];
}
}
[_renderedClusters addObject:cluster];
}
// Returns a marker at final position of |position| with attached |userData|.
// If animated is YES, animates from the closest point from |points|.
- (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);
}
marker.map = _mapView;
if (animated) {
[CATransaction begin];
[CATransaction setAnimationDuration:kGMUAnimationDuration];
marker.layer.latitude = position.latitude;
marker.layer.longitude = position.longitude;
[CATransaction commit];
}
return marker;
}
I had the similar problem 2 days ago and I just found the solution. Hope it will be useful for you.
For example you have a mapView and you set a delegate to it in right place:
[self.mapView setDelegate:self];
Then you need to implement the optional method from GMSMapViewDelegate protocol:
- (void)mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position {
[self performSelector:#selector(updateMarkers) withObject:nil afterDelay:0.2];
}
I use delay 0.2 seconds, because markers wouldn't update their icons if you'll use smaller value.
The next step is implement method for updating icons:
-(void) updateMarkers {
// "mapView" property in your self.mapView has type GMSVectorMapView,
//and it is hidden, so you can't get like self.mapView.mapView
id vectorMap = [self.mapView valueForKey:#"mapView"];
// "accessibilityItems" - property that have all items in visible part of map.
NSMutableArray* GMSMarkersArray = [vectorMap mutableArrayValueForKey:#"accessibilityItems"];
// Very often you'll get object of GMSPointOfInteretUIItem class, and you don't need it =)
NSMutableArray *discardedItems = [NSMutableArray array];
for (id item in GMSMarkersArray) {
if (![item isKindOfClass:[GMSMarker class]])
[discardedItems addObject:item];
}
[GMSMarkersArray removeObjectsInArray:discardedItems];
// If marker don't have icon image, he use default red pin, but property is still have nil-value ...
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"icon = nil"];
NSArray* singleMarkers = [GMSMarkersArray filteredArrayUsingPredicate:predicate];
// ... and here you can setup any icon you want, for all singles markers in visible part of map.
for(GMSMarker* marker in singleMarkers) {
marker.icon = [UIImage imageNamed:#"yourIcon.png"];
}
}
Also if you create your own marker and add it to cluster, you can get it from userData property of GMSMarker object in last loop. And for example you have there your custom marker with icon you want, just change last loop for something like:
for(GMSMarker* marker in singleMarkers) {
YourMarkerClass* yourMaker = marker.userData;
marker.icon = yourMaker.icon;
}
Sorry for possible mistakes and ask the questions if you don't understand something =)

MKMapView won't respond to touching or pinching

So I have a MKMapVie object and it is set up as follows:
self.mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0, 0,
[Screen width],
[Screen height])];
self.mapView.mapType = MKMapTypeSatellite;
self.mapView.userInteractionEnabled = YES;
self.mapView.scrollEnabled = YES;
self.mapView.showsUserLocation = YES;
[self addSubview:mapView];
And I set its location to be the current user location with the following method.
- (void) locateUser {
float z2 = 0.003 * 4.0;
MKCoordinateSpan span = MKCoordinateSpanMake(z2, z2);
[self.mapView setRegion: MKCoordinateRegionMake(currentLocation, span) animated:YES];
}
However once the map zooms to the users location it will not allow me to move the map around or pinch or zoom. Is there something i'm missing?
Thanks.

Mapbox iOS - Fill polygon created dynamically

I am using Mapbox framework for maps and i want to fill polygons and the vertices of the polygon has given from the user touch.
Here is my code on user touch
- (void)singleTapOnMap:(RMMapView *)map at:(CGPoint)point
{
CLLocationCoordinate2D coord;
coord.latitude = [map pixelToCoordinate:point].latitude;
coord.longitude = [map pixelToCoordinate:point].longitude;
RMAnnotation *annotation = [[RMAnnotation alloc] initWithMapView:map coordinate:coord andTitle:#""];
annotation.userInfo = [[NSArray alloc]initWithObjects:[[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude], nil];
[map addAnnotation:annotation];
}
Delegate method
- (RMMapLayer *)mapView:(RMMapView *)mapView layerForAnnotation:(RMAnnotation *)annotation
{
if (annotation.isUserLocationAnnotation)
return nil;
CLLocation *location = [annotation.userInfo objectAtIndex:0];
RMShape *line = [[RMShape alloc] initWithView:annotation.mapView];
line.lineWidth = 3.0;
line.position = annotation.position;
line.lineColor = [UIColor redColor];
line.fillColor = [UIColor greenColor];
[line moveToCoordinate:location.coordinate];
[line addLineToCoordinate:lastLocation.coordinate];
return line;
}
I can draw the polygon but unable to fill it.
It seems to me that, according to the way that you are currently doing it, for each tap on the map you are creating a NEW annotation, each containing one line segment from the last location to the current tap location, which will not create a polygon, but rather a series of individual annotations containing only one line segment.
You will need to create a separate array of locations of vertices. As you add more locations via tapping it adds to the location array:
// #1 create the following iVars for keeping state of drawing and polygon vertices
bool isDrawingPolygon;
NSMutableArray *savedPolygonVertices;
// #2 in viewDidLoad or your init code, be sure to set the initial states
-(void)viewDidLoad
{
isDrawingPolygon = FALSE;
savedPolygonVertices = nil;
...
... REST OF VIEW DID LOAD OR INIT METHOD
// #3 Create an IBAction button method to trigger beginning of drawing dynamic polygon (a start button)
-(void)startCreatingPolygon
{
isDrawingPolygon = TRUE;
savedPolygonVertices = [[NSMutableArray alloc] initWithCapacity:50]; // Some arbitrary number of points
}
// #4 Begin adding location vertices whenever singleTapOnMap
-(void)singleTapOnMap:(RMMapView *)map at:(CGPoint)point
{
if (isDrawingPolygon)
{
CLLocationCoordinate2D coord;
coord.latitude = [map pixelToCoordinate:point].latitude;
coord.longitude = [map pixelToCoordinate:point].longitude;
[savedPolygonVertices addObject:coord];
RMAnnotation *annotation = [[RMAnnotation alloc] initWithMapView:map coordinate:coord andTitle:#"tempPolygon"]; // Give each temporary line annotation some common identifier "tempPolygon"
annotation.userInfo = [[NSArray alloc]initWithObjects:[[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude], nil];
[map addAnnotation:annotation];
}
}
// #5 When you tap the "stop" button, you would need to roll the location vertices in the array into ONE annotation object containing an RMShape polygon containing all the vertices for the polygon (and fill/line color attributes), and an identifier for that polygon.
-(void)stopCreatingPolygon // IBAction method for stop making polygon
{
isDrawingPolygon = FALSE;
RMAnnotation *annotation = [[RMAnnotation alloc] initWithMapView:self.mapView coordinate:coord andTitle:#"Polygon"];
annotation.userInfo = savedPolygonVertices;
[self.mapView addAnnotation:annotation];
savedPolygonVertices = nil;
for (RMAnnotation *ann in self.mapView.annotations)
{
if ([ann.title isEqualToString:#"tempPolygon"])
[self.mapView removeAnnotation:ann]; // Get rid of the temporary line segments
}
}
// #6 Then in layerForAnnotation, you would need to check for that identifier (annotation.title), and put the polygon wrapped in an if statement
- (RMMapLayer *)mapView:(RMMapView *)mapView layerForAnnotation:(RMAnnotation *)annotation
{
if (annotation.isUserLocationAnnotation)
return nil;
if ([annotation.title isEqualToString:#"tempPolygon"])
{
CLLocation *location = [annotation.userInfo objectAtIndex:0];
RMShape *line = [[RMShape alloc] initWithView:annotation.mapView];
line.lineWidth = 3.0;
line.position = annotation.position;
line.lineColor = [UIColor redColor];
line.fillColor = [UIColor greenColor];
[line moveToCoordinate:location.coordinate];
[line addLineToCoordinate:lastLocation.coordinate];
return line;
}
if ([annotation.title isEqualToString:#"Polygon"])
{
RMShape *shape = [[RMShape alloc] initWithView:self.mapView];
shape.lineWidth = 3.0;
shape.lineColor = [UIColor redColor];
shape.fillColor = [UIColor greenColor];
shape.fillRule= kCAFillRuleNonZero;
shape.lineJoin = kCALineJoinRound;
shape.lineCap = kCALineCapRound;
for (CLLocationCoordinate2D * location in annotation.userInfo){
// userInfo now contains all vertices between start & stop
[shape addLineToCoordinate:location];
}
return shape;
}
}
That should give you what you are looking for.
/blee/

How to add clicked annotations on mapbox using offline mbtiles files

I have already managed to use interactive offline mbtiles (created on TileMill) in order to:
Load more than 1000 points fast
Make them understand when user is clicking each point and show a popup with the name of each point
But I can't make the bubble with the name clickable again.
I use the following code to generate the layer for the annotation of each point
- (RMMapLayer *)mapView:(RMMapView *)mapView layerForAnnotation:(RMAnnotation *)annotation{
RMMarker *marker = [[RMMarker alloc] initWithMapboxMarkerImage:#"embassy"];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 50, 32)];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.image = annotation.userInfo;
marker.leftCalloutAccessoryView = imageView;
marker.canShowCallout = YES;
return marker;
}
and this is how I get the teaser and build my annotation from the mbtiles file:
- (void)singleTapOnMap:(RMMapView *)mapView at:(CGPoint)point{
[mapView removeAllAnnotations];
RMMBTilesSource *source = (RMMBTilesSource *)mapView.tileSource;
if ([source conformsToProtocol:#protocol(RMInteractiveSource)] && [source supportsInteractivity])
{
NSString *formattedOutput = [source formattedOutputOfType:RMInteractiveSourceOutputTypeTeaser
forPoint:point
inMapView:mapView];
if (formattedOutput && [formattedOutput length])
{
// parse the country name out of the content
//
NSUInteger startOfCountryName = [formattedOutput rangeOfString:#"<strong>"].location + [#"<strong>" length];
NSUInteger endOfCountryName = [formattedOutput rangeOfString:#"</strong>"].location;
NSString *countryName = [formattedOutput substringWithRange:NSMakeRange(startOfCountryName, endOfCountryName - startOfCountryName)];
// parse the flag image out of the content
//
NSUInteger startOfFlagImage = [formattedOutput rangeOfString:#"base64,"].location + [#"base64," length];
NSUInteger endOfFlagImage = [formattedOutput rangeOfString:#"\" style"].location;
UIImage *flagImage = [UIImage imageWithData:[NSData dataFromBase64String:[formattedOutput substringWithRange:NSMakeRange(startOfFlagImage, endOfFlagImage)]]];
RMAnnotation *annotation = [RMAnnotation annotationWithMapView:mapView coordinate:[mapView pixelToCoordinate:point] andTitle:countryName];
annotation.userInfo = flagImage;
[mapView addAnnotation:annotation];
[mapView selectAnnotation:annotation animated:YES];
}
}
}
UPDATED
I figured out how to do that by using a leftCalloutAccessoryView on the marker (I added the following at the end of layerForAnnotation method :
marker.leftCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
and used the following delegate method to track the event:
-(void)tapOnCalloutAccessoryControl:(UIControl *)control forAnnotation:(RMAnnotation *)annotation onMap:(RMMapView *)map{
NSLog(#"I will now pass to the next screen ! YEAH! %#",annotation.title);
}
The problem now is that I want to get rid off the left calloutAccesoryView. Any suggestions?
None of what you have here about the map is needed. What you really want is the callout (which is of type SMCalloutView, a dependent project to the Mapbox SDK that you are using) to have its clicks registered.
Check out this issue for more details:
https://github.com/mapbox/mapbox-ios-sdk/issues/422

MapBox: How to remove a shape and draw another shape?

I created annotation for shape
_path = [RMAnnotation annotationWithMapView:_mapView
coordinate: _userLocation.coordinate
andTitle:#"Path"];
[_mapView addAnnotation:_path];
in delegate I wrote
- (RMMapLayer *)mapView:(RMMapView *)mapView layerForAnnotation:(RMAnnotation *)annotation
{
if ([annotation.title isEqualToString:#"Path"])
{
_lineBetweenTwoBeacon = [[RMShape alloc] initWithView:mapView];
_lineBetweenTwoBeacon.lineColor = [UIColor redColor];
_lineBetweenTwoBeacon.lineWidth = 10.0f;
return _lineBetweenTwoBeacon;
}
else
{
marker = [[RMMarker alloc] initWithUIImage:[UIImage imageNamed:#"userPin"]];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 80, 80)];
imageView.contentMode = UIViewContentModeScaleAspectFit;
marker.leftCalloutAccessoryView = imageView;
return marker;
}
}
Next step I draw shape
[_lineBetweenTwoBeacon addQuadCurveToCoordinate:firstBeaconCoord controlCoordinate:secondBeaconCoord];
But how to remove all shapes from the map and add new shape.
Now the shape lay to the shape, it's not correct.
Will be better if _lineBetweenTwoBeacon redraw every time.
Thank you, for help!
When you manually create an RMShape, you need to tell it where to move and to draw after creating it with methods like -moveToCoordinate: and -addLineToCoordinate:. If you just have basic needs, I would recommend trying RMPolylineAnnotation, which handles the drawing for you.

Resources