I try to limit core-plot zoom between 2 value, more or less my code work, but sometime it don't work properly, this is the code:
- (void) handlePinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer{
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
scale = [gestureRecognizer scale];
}
else if (gestureRecognizer.state == UIGestureRecognizerStateChanged) {
CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)self.graph.defaultPlotSpace;
lenghtX = [self getRoundedValue:max_X - min_X];
lenghtY = [self getRoundedValue:max_Y - min_Y];
if (lenghtX <= 100 && lenghtX >= 2) {
double stepScale = lenghtX / 10;
if ([gestureRecognizer scale] < scale) {
min_X = [self getRoundedValue:plotSpace.xRange.minLimitDouble - stepScale];
max_X = [self getRoundedValue:plotSpace.xRange.maxLimitDouble + stepScale];
min_Y = [self getRoundedValue:plotSpace.yRange.minLimitDouble - stepScale];
max_Y = [self getRoundedValue:plotSpace.yRange.maxLimitDouble + stepScale];
}
else {
min_X = [self getRoundedValue:plotSpace.xRange.minLimitDouble + stepScale];
max_X = [self getRoundedValue:plotSpace.xRange.maxLimitDouble - stepScale];
min_Y = [self getRoundedValue:plotSpace.yRange.minLimitDouble + stepScale];
max_Y = [self getRoundedValue:plotSpace.yRange.maxLimitDouble - stepScale];
}
}
else {
if (lenghtX > 100) {
double rest = [self getRoundedValue:(lenghtX - 100)/2];
min_X = [self getRoundedValue:min_X + rest];
max_X = [self getRoundedValue:max_X - rest];
min_Y = [self getRoundedValue:min_Y + rest];
max_Y = [self getRoundedValue:max_Y - rest];
NSLog(#"Lenght:%f Rest: %f",lenghtX,rest);
}
else if (lenghtX < 2) {
double rest = [self getRoundedValue:(2 - lenghtX)/2];
min_X = [self getRoundedValue:min_X - rest];
max_X = [self getRoundedValue:max_X + rest];
min_Y = [self getRoundedValue:min_Y - rest];
max_Y = [self getRoundedValue:max_Y + rest];
NSLog(#"Lenght:%f Rest: %f",lenghtX,rest);
}
}
lenghtX = [self getRoundedValue:max_X - min_X];
lenghtY = [self getRoundedValue:max_Y - min_Y];
NSLog(#"Lenght: %f",lenghtX);
plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromDouble(min_X) length:CPTDecimalFromFloat(lenghtX)];
plotSpace.yRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromDouble(min_Y) length:CPTDecimalFromFloat(lenghtY)];
scale = [gestureRecognizer scale];
}
else if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
if (lenghtX <= 100 && lenghtX >= 2) {
CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)self.graph.defaultPlotSpace;
min_X = [self getRoundedValue:plotSpace.xRange.minLimitDouble];
max_X = [self getRoundedValue:plotSpace.xRange.maxLimitDouble];
step_punto = (max_X-min_X)/numero_punti;
[self get_N_Value:numero_punti from:min_X toMax:max_X];
}
}
}
- (double) getRoundedValue:(double) value {
double roundedValue = round(value * 100.0) / 100.0;
return roundedValue;
}
In this code I check the length of visible x, if is >100 or <2 I set it 2 or 100, the problem is that not always the final value is inside this renege. The second problem is that when I zoom to the limit (for example when I zoom in) I want to block the zoom while now in the proximity of the limit there is a little zoom out and then a little zoom in (repeated).
How can I improve the code?
Remove the outer if-else statement that begins with if (lenghtX <= 100 && lenghtX >= 2). Always compute the scaled ranges and then check the length and adjust as necessary to bring it back in the 2-100 range.
You should check the accepted answer here:
Core Plot - set zoom level to show only part of graph
This is using global X and Y range setting. Which works perfectly for me.
There is also a mention in the comments for using the CPTPlotSpaceDelegate's method:
-(BOOL)plotSpace:(CPTPlotSpace *)space shouldScaleBy:(CGFloat)interactionScale aboutPoint:(CGPoint)interactionPoint;
Which would allow you to maintain a maximum zoom level. Note that minimum zoom level is actually handled by setting global X and Y ranges.
Related
I'm trying to make a game, following this tutorial.
The issue comes from the fact that I am using ActionScript 3.0 whereas the tutorial was written using ActionScript 2.0.
Regarding the sight of the enemy, I have turned this code:
onClipEvent (enterFrame) {
dist_x = _root.hero._x-_x;
dist_y = _root.hero._y-_y;
dist = Math.sqrt(dist_x*dist_x+dist_y*dist_y);
angle = Math.atan(dist_y/dist_x)/(Math.PI/180);
if (dist_x<0) {
angle += 180;
}
if (dist_x>=0 && dist_y<0) {
angle += 360;
}
wall_collision = 0;
for (x=1; x<=dist; x++) {
point_x = _x+x*Math.cos(angle*Math.PI/180);
point_y = _y+x*Math.sin(angle*Math.PI/180);
if (_root.wall.hitTest(point_x, point_y, true)) {
wall_collision = 100;
break;
}
}
_root.line._x = _x;
_root.line._y = _y;
_root.line._rotation = angle;
_root.line._alpha = 100-wall_collision;
}
Into that:
// calculate rotation based on target
_dx = this.x - _root.hero.x;
_dy = this.y - _root.hero.y;
// which way to rotate
_rotateTo = getDegrees(getRadians(_dx, _dy));
// keep rotation positive, between 0 and 360 degrees
if (_rotateTo > barrel.rotation + 90) _rotateTo -= 360;
if (_rotateTo < barrel.rotation - 90) _rotateTo += 360;
// ease rotation
_trueRotation = (_rotateTo - barrel.rotation) / _rotateSpeedMax;
// update rotation
barrel.rotation += _trueRotation;
wall_collision = 0;
OuterLoop: for (var xi=1; xi<=_dx; xi++)
{
var point_x:Number = this.x + xi*Math.cos(_rotateTo);
var point_y:Number = this.y + xi*Math.sin(_rotateTo);
if(_root.wall.hitTestPoint(point_x, point_y, true))
{
trace("HIT");
wall_collision = 100;
break OuterLoop;
}
}
_root.sight.x = this.x;
_root.sight.y = this.y;
_root.sight.rotation += _trueRotation;
_root.sight.alpha = 100 - wall_collision;
But the it does not work.
The rotation do work fine, but the whole "alpha = 0 if player is behind a wall" does not work.
Please help me resolving the issue.
Try the following:
// calculate rotation based on target
_dx = _root.hero.x-this.x;
_dy = _root.hero.y-this.y;
// The full distance is missing from your AS3 code
_dist = Math.sqrt(_dx*_dx+_dy*_dy);
// Return the old good approach for finding angle
angle = Math.atan(_dy/_dx)/(Math.PI/180);
if (_dx<0) {
_angle += 180;
}
if (_dx>=0 && _dy<0) {
_angle += 360;
}
wall_collision = 0;
OuterLoop: for (var xi=1; xi<=_dist; xi++)
{
var point_x:Number = this.x + xi*Math.cos(_angle*Math.PI/180);
var point_y:Number = this.y + xi*Math.sin(_angle*Math.PI/180);
if(_root.wall.hitTestPoint(point_x, point_y, true))
{
trace("HIT");
wall_collision = 100;
break OuterLoop;
}
}
_root.sight.x = this.x;
_root.sight.y = this.y;
_root.sight.rotation = _angle;
// Alpha changed from [0, 100] scale to [0, 1] scale.
_root.sight.alpha = (100 - wall_collision) * 0.01;
Information on alpha in ActionScript 3.0.
As per AS3 reference, alpha is from 0 to 1, not 0 to 100. That would suggest
`_root.sight.alpha = (100 - wall_collision)/100.0´
might work.
Can You try the following code. I have no prev exp with flash, but seems like You missed something.
The iterator xi should take values in range of distance, not only by one axis dx.
// calculate rotation based on target
_dx = this.x - _root.hero.x;
_dy = this.y - _root.hero.y;
// the iteration is by distance in original article mentioned so
// keep dist
//=================================
_dist = Math.sqrt(_dx*_dx+_dy*_dy);
// which way to rotate
_rotateTo = getDegrees(getRadians(_dx, _dy));
// keep rotation positive, between 0 and 360 degrees
if (_rotateTo > barrel.rotation + 90) _rotateTo -= 360;
if (_rotateTo < barrel.rotation - 90) _rotateTo += 360;
// ease rotation
_trueRotation = (_rotateTo - barrel.rotation) / _rotateSpeedMax;
// update rotation
barrel.rotation += _trueRotation;
wall_collision = 0;
// xi iterations are to a distance
//== =======
OuterLoop: for (var xi=1; xi<=_dist; xi++)
{
var point_x:Number = this.x + xi*Math.cos(_rotateTo);
var point_y:Number = this.y + xi*Math.sin(_rotateTo);
if(_root.wall.hitTestPoint(point_x, point_y, true))
{
trace("HIT");
wall_collision = 100;
break OuterLoop;
}
}
_root.sight.x = this.x;
_root.sight.y = this.y;
_root.sight.rotation += _trueRotation;
// EDITED AFTER OTHERS SOLVED
// was
//_root.sight.alpha = 100 - wall_collision;
// should be:
// Alpha changed from [0, 100] scale to [0, 1] scale.
_root.sight.alpha = (100 - wall_collision) * 0.01;
// END OF SOLUTION
There is only slight modification to Your original code, marked by preceding //=====
EDIT:
And the winner is transparency range. Still, I do recommend to iterate to a distance, not to _dx.
I am centering a map around the user's location or alternatively around a default location with a zoom level of roughly 30 miles. The problem is I can zoom in but cannot zoom out.
Whenever I try to zoom out on my iPhone 5C, the map immediately zooms back in when I remove my finger pinch.
I tried adding the mapView methods regionDidChangeAnimated and didChangeDragState to make certain I was resetting the region and center of the view, but neither of those seem to changed the ability to zoom out.
Cannot figure out what I missing here. Thanks for your help!
//
// MapViewController.m
#import "MapViewController.h"
#import "MeetFetcher.h"
#import "MeetLocationAnnotation.h"
#import "MyAnnotations.h"
NSInteger const redPin = 0;
NSInteger const greenPin = 1;
NSInteger const purplePin = 2;
#interface MapViewController() <MKMapViewDelegate, CLLocationManagerDelegate>
#property (strong, nonatomic) IBOutlet MKMapView *mapView;
#end
#implementation MapViewController
#synthesize mapView = _mapView;
#synthesize annotations = _annotations;
#synthesize delegate = _delegate;
#synthesize beginningDateForSearch = _beginningDateForSearch;
#synthesize levelOfCompetition = _levelOfCompetition;
#synthesize meets = _meets;
#synthesize myLocationMngr = _myLocationMngr;
#pragma mark - Synchronize Model and View
- (NSArray *)mapAnnotations
{
NSMutableArray *meetAnnotations = [NSMutableArray arrayWithCapacity:[self.meets count]];
NSInteger iCount = 0;
for (NSDictionary *meet in self.meets) {
NSLog(#"In [%# %#], iCount = %d and meet = %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), iCount, meet);
[meetAnnotations addObject:[MeetLocationAnnotation annotationForMeet:meet]];
iCount++;
NSLog(#"\n \n meetAnnotations = %#",meetAnnotations);
}
return meetAnnotations;
}
- (void)updateMapView
{
if (self.mapView.annotations) [self.mapView removeAnnotations:self.mapView.annotations];
if (self.annotations) [self.mapView addAnnotations:self.annotations];
}
- (void)setMapView:(MKMapView *)mapView
{
_mapView = mapView;
[self updateMapView];
}
- (void)setAnnotations:(NSArray *)annotations
{
_annotations = annotations;
[self updateMapView];
}
- (void)setMeets:(NSArray *)meets
{
if (_meets != meets) {
_meets = meets;
}
// Model changed, so update our View in map
NSLog(#"meets count in setMeets = %d", [meets count]);
NSArray* listOfAnnotations = [self mapAnnotations];
[self setAnnotations:listOfAnnotations];
[self updateMapView];
}
#pragma mark - MKMapViewDelegate
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
MKPinAnnotationView *aView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"MapVC"];
if (!aView) {
aView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"MapVC"];
aView.canShowCallout = YES;
aView.rightCalloutAccessoryView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
aView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
aView.pinColor = myPinColor;
// could put a rightCalloutAccessoryView here
} else {
aView.annotation = annotation;
}
// [(UIImageView *)aView.rightCalloutAccessoryView setImage:nil]; // Could replace rightCallOutButton with image.
return aView;
}
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)aView
{
UIImage *image = [self.delegate mapViewController:self imageForAnnotation:aView.annotation];
[(UIImageView *)aView.leftCalloutAccessoryView setImage:image];
}
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
NSLog(#"callout accessory tapped for annotation %#", [view.annotation title]);
}
#pragma mark - View Controller Lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.mapView.delegate = self;
self.myLocationMngr = [[CLLocationManager alloc] init];
self.myLocationMngr.delegate = self;
self.myLocationMngr.desiredAccuracy = 500; // 500 meters.
if( [CLLocationManager locationServicesEnabled] ) {
myPinColor = MKPinAnnotationColorGreen;
[self.myLocationMngr startUpdatingLocation];
} else {
CLLocationCoordinate2D defaultCoordinateWhenCLLocationDisabled;
defaultCoordinateWhenCLLocationDisabled.latitude = 45.194014;
defaultCoordinateWhenCLLocationDisabled.longitude = -117.862015;
MyAnnotations *annotation =
[[MyAnnotations alloc] initWithCoordinates:defaultCoordinateWhenCLLocationDisabled
title:DEFAULT_LOCATION_TITLE
subTitle:DEFAULT_LOCATION_SUBTITLE
selectedPinColor:redPin];
// [self.mapView addAnnotation:annotation];
[self mapView:self.mapView didAddAnnotationViews:(NSArray *)annotation];
}
//Set some paramater for the location object.
[self.myLocationMngr setDistanceFilter:kCLDistanceFilterNone];
[self.myLocationMngr setDesiredAccuracy:kCLLocationAccuracyBest];
//Set the first launch instance variable to allow the map to zoom on the user location when first launched.
firstLaunch=YES;
}
- (void)viewDidUnload
{
[super viewDidUnload];
[self setMapView:nil];
}
-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *lastLocation =[locations lastObject];
CLLocationAccuracy accuracy = [lastLocation horizontalAccuracy];
NSLog(#"Received location %# with accuracy %f", lastLocation, accuracy);
if(accuracy < 100.0)
{
MKCoordinateSpan span = MKCoordinateSpanMake(0.14, 0.14);
region = MKCoordinateRegionMake([lastLocation coordinate], span);
[_mapView setRegion:region animated:YES];
[manager stopUpdatingLocation];
}
}
- (void)mapView:(MKMapView *)mv didAddAnnotationViews:(MKAnnotationView *)annotation
{
//Zoom back to the user location after adding a new set of annotations.
//Get the center point of the visible map.
CLLocationCoordinate2D centre = [mv centerCoordinate];
NSLog(#"centerCoordinate.Latitude = %f and centerCoordinate.Longitude = %f",centre.latitude, centre.longitude);
// MKCoordinateRegion region;
//If this is the first launch of the app then set the center point of the map to the user's location.
if (firstLaunch) {
region = MKCoordinateRegionMakeWithDistance(_myLocationMngr.location.coordinate,50000,50000);
firstLaunch=NO;
}else {
//Set the center point to the visible region of the map and change the radius to match the search radius passed to the Google query string.
region = MKCoordinateRegionMakeWithDistance(centre,currentDist,currentDist);
}
//Set the visible region of the map.
[mv setRegion:region animated:YES];
}
-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
//Get the east and west points on the map so you can calculate the distance (zoom level) of the current map view.
MKMapRect mRect = self.mapView.visibleMapRect;
MKMapPoint eastMapPoint = MKMapPointMake(MKMapRectGetMinX(mRect), MKMapRectGetMidY(mRect));
MKMapPoint westMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), MKMapRectGetMidY(mRect));
//Set your current distance instance variable.
currentDist = MKMetersBetweenMapPoints(eastMapPoint, westMapPoint);
//Set your current center point on the map instance variable.
currentCentre = self.mapView.centerCoordinate;
region = MKCoordinateRegionMakeWithDistance(currentCentre,currentDist,currentDist);
}
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState
{
MKMapRect mRect = self.mapView.visibleMapRect;
MKMapPoint eastMapPoint = MKMapPointMake(MKMapRectGetMinX(mRect), MKMapRectGetMidY(mRect));
MKMapPoint westMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), MKMapRectGetMidY(mRect));
//Set your current distance instance variable.
currentDist = MKMetersBetweenMapPoints(eastMapPoint, westMapPoint);
//Set your current center point on the map instance variable.
currentCentre = self.mapView.centerCoordinate;
region = MKCoordinateRegionMakeWithDistance(currentCentre,currentDist,currentDist);
}
- (IBAction)refresh:(id)sender
{
// might want to use introspection to be sure sender is UIBarButtonItem
// (if not, it can skip the spinner)
// that way this method can be a little more generic
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[spinner startAnimating];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:spinner];
dispatch_queue_t downloadQueue = dispatch_queue_create("meet downloader", NULL);
dispatch_async(downloadQueue, ^{
NSArray *meetsWithCoordinates = [MeetFetcher selectedGeoreferencedMeets:_beginningDateForSearch andCompetitionLevel:_levelOfCompetition];
dispatch_async(dispatch_get_main_queue(), ^{
self.navigationItem.leftBarButtonItem = sender;
NSLog(#"meetsWithCoordinates count = %d", [meetsWithCoordinates count]);
// NSLog(#"%#",meetsWithCoordinates);
myPinColor = MKPinAnnotationColorPurple;
self.meets = meetsWithCoordinates;
});
});
}
#end
You shouldn't call didAddAnnotationViews, you should add the annotation and let iOS call the delegate method if it does indeed add the annotations.
Every time annotationViews are added you're changing the region. I suspect you meant to do that every time new annotations are added so add the setRegion call in the same place you addAnnotations. The views might be being added on a different sequence than you expect, for example panning or zooming out might reveal more annotations and thus the views for those annotations are drawn.
So here is what I learned using Anna and Craig's suggestions. First off I was using the wrong method to determine whether app had permission to use location services. Then I put a switch in to determine whether the initial zoom level had already been set.
- (void)viewDidLoad
{
[super viewDidLoad];
self.mapView.delegate = self;
self.myLocationMngr = [[CLLocationManager alloc] init];
self.myLocationMngr.delegate = self;
self.myLocationMngr.desiredAccuracy = 500; // 500 meters.
if([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorized) {
myPinColor = MKPinAnnotationColorGreen;
[self.myLocationMngr startUpdatingLocation];
if (!haveSetZoomLevel) {
CLLocation *currentLocation = [self.myLocationMngr location];
CLLocationCoordinate2D currentCoordinates = [currentLocation coordinate];
region = MKCoordinateRegionMakeWithDistance(currentCoordinates,50000,50000);
[self.mapView setRegion:region animated:YES];
haveSetZoomLevel = YES;
}
}
//Set some paramater for the location object.
[self.myLocationMngr setDistanceFilter:kCLDistanceFilterNone];
[self.myLocationMngr setDesiredAccuracy:kCLLocationAccuracyBest];
}
Then I used
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
if([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorized) {
// zoom to user's location
} else {
// zoom to a default location
}
}
I am using Google Maps SDK in my iOS app, and I need to group markers which are very close to each other - basically need to use marker clustering like its shown in the attached url. I am able to get this functionality in the Android maps SDK, but I didn't find any library for the iOS Google Maps SDK.
Can you please suggest any library for this? Or suggest a way to implement a custom library for this?
(Source of this picture)
To understand the underlying concept of this double map solution, please have a look at this WWDC 2011 video (from 22'30). The Map kit code is directly extracted from this video except a few things that I described in a few notes. The Google Map SDK solution is just an adaptation.
Main idea: a map is hidden and holds every single annotation, including the merged ones (allAnnotationMapView in my code). Another is visible and shows only the cluster's annotations or the annotation if it's single (mapView in my code).
Second main idea: I divide the visible map (plus a margin) into squares, and every annotation in a specific square are merged into one annotation.
The code I use for Google Maps SDK (Please note that I wrote this when markers property were available on GMSMapView class. It's not anymore but you can keep track of all the marker you add in your own array, and use this array instead of calling mapView.markers):
- (void)loadView {
[super loadView];
self.mapView = [[GMSMapView alloc] initWithFrame:self.view.frame];
self.mapView.delegate = self;
self.allAnnotationMapView = [[GMSMapView alloc] initWithFrame:self.view.frame]; // can't be zero or you'll have weard results (I don't remember exactly why)
self.view = self.mapView;
UIPinchGestureRecognizer* pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(didZoom:)];
[pinchRecognizer setDelegate:self];
[self.mapView addGestureRecognizer:pinchRecognizer];
}
- (void)didZoom:(UIGestureRecognizer*)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded){
[self updateVisibleAnnotations];
}
}
- (float)distanceFrom:(CGPoint)point1 to:(CGPoint)point2 {
CGFloat xDist = (point2.x - point1.x);
CGFloat yDist = (point2.y - point1.y);
return sqrt((xDist * xDist) + (yDist * yDist));
}
- (NSSet *)annotationsInRect:(CGRect)rect forMapView:(GMSMapView *)mapView {
GMSProjection *projection = self.mapView.projection; //always take self.mapView because it is the only one zoomed on screen
CLLocationCoordinate2D southWestCoordinates = [projection coordinateForPoint:CGPointMake(rect.origin.x, rect.origin.y + rect.size.height)];
CLLocationCoordinate2D northEastCoordinates = [projection coordinateForPoint:CGPointMake(rect.origin.x + rect.size.width, rect.origin.y)];
NSMutableSet *annotations = [NSMutableSet set];
for (GMSMarker *marker in mapView.markers) {
if (marker.position.latitude < southWestCoordinates.latitude || marker.position.latitude >= northEastCoordinates.latitude) {
continue;
}
if (marker.position.longitude < southWestCoordinates.longitude || marker.position.longitude >= northEastCoordinates.longitude) {
continue;
}
[annotations addObject:marker.userData];
}
return annotations;
}
- (GMSMarker *)viewForAnnotation:(PointMapItem *)item forMapView:(GMSMapView *)mapView{
for (GMSMarker *marker in mapView.markers) {
if (marker.userData == item) {
return marker;
}
}
return nil;
}
- (void)updateVisibleAnnotations {
static float marginFactor = 1.0f;
static float bucketSize = 100.0f;
CGRect visibleMapRect = self.view.frame;
CGRect adjustedVisibleMapRect = CGRectInset(visibleMapRect, -marginFactor * visibleMapRect.size.width, -marginFactor * visibleMapRect.size.height);
double startX = CGRectGetMinX(adjustedVisibleMapRect);
double startY = CGRectGetMinY(adjustedVisibleMapRect);
double endX = CGRectGetMaxX(adjustedVisibleMapRect);
double endY = CGRectGetMaxY(adjustedVisibleMapRect);
CGRect gridMapRect = CGRectMake(0, 0, bucketSize, bucketSize);
gridMapRect.origin.y = startY;
while(CGRectGetMinY(gridMapRect) <= endY) {
gridMapRect.origin.x = startX;
while (CGRectGetMinX(gridMapRect) <= endX) {
NSSet *allAnnotationsInBucket = [self annotationsInRect:gridMapRect forMapView:self.allAnnotationMapView];
NSSet *visibleAnnotationsInBucket = [self annotationsInRect:gridMapRect forMapView:self.mapView];
NSMutableSet *filteredAnnotationsInBucket = [[allAnnotationsInBucket objectsPassingTest:^BOOL(id obj, BOOL *stop) {
BOOL isPointMapItem = [obj isKindOfClass:[PointMapItem class]];
BOOL shouldBeMerged = NO;
if (isPointMapItem) {
PointMapItem *pointItem = (PointMapItem *)obj;
shouldBeMerged = pointItem.shouldBeMerged;
}
return shouldBeMerged;
}] mutableCopy];
NSSet *notMergedAnnotationsInBucket = [allAnnotationsInBucket objectsPassingTest:^BOOL(id obj, BOOL *stop) {
BOOL isPointMapItem = [obj isKindOfClass:[PointMapItem class]];
BOOL shouldBeMerged = NO;
if (isPointMapItem) {
PointMapItem *pointItem = (PointMapItem *)obj;
shouldBeMerged = pointItem.shouldBeMerged;
}
return isPointMapItem && !shouldBeMerged;
}];
for (PointMapItem *item in notMergedAnnotationsInBucket) {
[self addAnnotation:item inMapView:self.mapView animated:NO];
}
if(filteredAnnotationsInBucket.count > 0) {
PointMapItem *annotationForGrid = (PointMapItem *)[self annotationInGrid:gridMapRect usingAnnotations:filteredAnnotationsInBucket];
[filteredAnnotationsInBucket removeObject:annotationForGrid];
annotationForGrid.containedAnnotations = [filteredAnnotationsInBucket allObjects];
[self removeAnnotation:annotationForGrid inMapView:self.mapView];
[self addAnnotation:annotationForGrid inMapView:self.mapView animated:NO];
if (filteredAnnotationsInBucket.count > 0){
// [self.mapView deselectAnnotation:annotationForGrid animated:NO];
}
for (PointMapItem *annotation in filteredAnnotationsInBucket) {
// [self.mapView deselectAnnotation:annotation animated:NO];
annotation.clusterAnnotation = annotationForGrid;
annotation.containedAnnotations = nil;
if ([visibleAnnotationsInBucket containsObject:annotation]) {
CLLocationCoordinate2D actualCoordinate = annotation.coordinate;
[UIView animateWithDuration:0.3 animations:^{
annotation.coordinate = annotation.clusterAnnotation.coordinate;
} completion:^(BOOL finished) {
annotation.coordinate = actualCoordinate;
[self removeAnnotation:annotation inMapView:self.mapView];
}];
}
}
}
gridMapRect.origin.x += bucketSize;
}
gridMapRect.origin.y += bucketSize;
}
}
- (PointMapItem *)annotationInGrid:(CGRect)gridMapRect usingAnnotations:(NSSet *)annotations {
NSSet *visibleAnnotationsInBucket = [self annotationsInRect:gridMapRect forMapView:self.mapView];
NSSet *annotationsForGridSet = [annotations objectsPassingTest:^BOOL(id obj, BOOL *stop) {
BOOL returnValue = ([visibleAnnotationsInBucket containsObject:obj]);
if (returnValue) {
*stop = YES;
}
return returnValue;
}];
if (annotationsForGridSet.count != 0) {
return [annotationsForGridSet anyObject];
}
CGPoint centerMapPoint = CGPointMake(CGRectGetMidX(gridMapRect), CGRectGetMidY(gridMapRect));
NSArray *sortedAnnotations = [[annotations allObjects] sortedArrayUsingComparator:^(id obj1, id obj2) {
CGPoint mapPoint1 = [self.mapView.projection pointForCoordinate:((PointMapItem *)obj1).coordinate];
CGPoint mapPoint2 = [self.mapView.projection pointForCoordinate:((PointMapItem *)obj2).coordinate];
CLLocationDistance distance1 = [self distanceFrom:mapPoint1 to:centerMapPoint];
CLLocationDistance distance2 = [self distanceFrom:mapPoint2 to:centerMapPoint];
if (distance1 < distance2) {
return NSOrderedAscending;
}
else if (distance1 > distance2) {
return NSOrderedDescending;
}
return NSOrderedSame;
}];
return [sortedAnnotations objectAtIndex:0];
return nil;
}
- (void)addAnnotation:(PointMapItem *)item inMapView:(GMSMapView *)mapView {
[self addAnnotation:item inMapView:mapView animated:YES];
}
- (void)addAnnotation:(PointMapItem *)item inMapView:(GMSMapView *)mapView animated:(BOOL)animated {
GMSMarker *marker = [[GMSMarker alloc] init];
GMSMarkerAnimation animation = kGMSMarkerAnimationNone;
if (animated) {
animation = kGMSMarkerAnimationPop;
}
marker.appearAnimation = animation;
marker.title = item.title;
marker.icon = [[AnnotationsViewUtils getInstance] imageForItem:item];
marker.position = item.coordinate;
marker.map = mapView;
marker.userData = item;
// item.associatedMarker = marker;
}
- (void)addAnnotations:(NSArray *)items inMapView:(GMSMapView *)mapView {
[self addAnnotations:items inMapView:mapView animated:YES];
}
- (void)addAnnotations:(NSArray *)items inMapView:(GMSMapView *)mapView animated:(BOOL)animated {
for (PointMapItem *item in items) {
[self addAnnotation:item inMapView:mapView];
}
}
- (void)removeAnnotation:(PointMapItem *)item inMapView:(GMSMapView *)mapView {
// Try to make that work because it avoid loopigng through all markers each time we just want to delete one...
// Plus, your associatedMarker property should be weak to avoid memory cycle because userData hold strongly the item
// GMSMarker *marker = item.associatedMarker;
// marker.map = nil;
for (GMSMarker *marker in mapView.markers) {
if (marker.userData == item) {
marker.map = nil;
}
}
}
- (void)removeAnnotations:(NSArray *)items inMapView:(GMSMapView *)mapView {
for (PointMapItem *item in items) {
[self removeAnnotation:item inMapView:mapView];
}
}
A few notes:
PointMapItem is my annotation data class (id<MKAnnotation> if we were working with Map kit).
Here I use a shouldBeMerged property on PointMapItem because there are some annotations I don't want to merge. If you do not need this, remove the part that is using it or set shouldBeMerged to YES for all your annotations. Though, you should probably keep the class testing if you don't want to merge user location!
When you want to add annotations, add them to the hidden allAnnotationMapView and call updateVisibleAnnotation. updateVisibleAnnotation method is responsible for choosing which annotations to merge and which to show. It will then add the annotation to mapView which is visible.
For Map Kit I use the following code:
- (void)didZoom:(UIGestureRecognizer*)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded){
[self updateVisibleAnnotations];
}
}
- (void)updateVisibleAnnotations {
static float marginFactor = 2.0f;
static float bucketSize = 50.0f;
MKMapRect visibleMapRect = [self.mapView visibleMapRect];
MKMapRect adjustedVisibleMapRect = MKMapRectInset(visibleMapRect, -marginFactor * visibleMapRect.size.width, -marginFactor * visibleMapRect.size.height);
CLLocationCoordinate2D leftCoordinate = [self.mapView convertPoint:CGPointZero toCoordinateFromView:self.view];
CLLocationCoordinate2D rightCoordinate = [self.mapView convertPoint:CGPointMake(bucketSize, 0) toCoordinateFromView:self.view];
double gridSize = MKMapPointForCoordinate(rightCoordinate).x - MKMapPointForCoordinate(leftCoordinate).x;
MKMapRect gridMapRect = MKMapRectMake(0, 0, gridSize, gridSize);
double startX = floor(MKMapRectGetMinX(adjustedVisibleMapRect) / gridSize) * gridSize;
double startY = floor(MKMapRectGetMinY(adjustedVisibleMapRect) / gridSize) * gridSize;
double endX = floor(MKMapRectGetMaxX(adjustedVisibleMapRect) / gridSize) * gridSize;
double endY = floor(MKMapRectGetMaxY(adjustedVisibleMapRect) / gridSize) * gridSize;
gridMapRect.origin.y = startY;
while(MKMapRectGetMinY(gridMapRect) <= endY) {
gridMapRect.origin.x = startX;
while (MKMapRectGetMinX(gridMapRect) <= endX) {
NSSet *allAnnotationsInBucket = [self.allAnnotationMapView annotationsInMapRect:gridMapRect];
NSSet *visibleAnnotationsInBucket = [self.mapView annotationsInMapRect:gridMapRect];
NSMutableSet *filteredAnnotationsInBucket = [[allAnnotationsInBucket objectsPassingTest:^BOOL(id obj, BOOL *stop) {
BOOL isPointMapItem = [obj isKindOfClass:[PointMapItem class]];
BOOL shouldBeMerged = NO;
if (isPointMapItem) {
PointMapItem *pointItem = (PointMapItem *)obj;
shouldBeMerged = pointItem.shouldBeMerged;
}
return shouldBeMerged;
}] mutableCopy];
NSSet *notMergedAnnotationsInBucket = [allAnnotationsInBucket objectsPassingTest:^BOOL(id obj, BOOL *stop) {
BOOL isPointMapItem = [obj isKindOfClass:[PointMapItem class]];
BOOL shouldBeMerged = NO;
if (isPointMapItem) {
PointMapItem *pointItem = (PointMapItem *)obj;
shouldBeMerged = pointItem.shouldBeMerged;
}
return isPointMapItem && !shouldBeMerged;
}];
for (PointMapItem *item in notMergedAnnotationsInBucket) {
[self.mapView addAnnotation:item];
}
if(filteredAnnotationsInBucket.count > 0) {
PointMapItem *annotationForGrid = (PointMapItem *)[self annotationInGrid:gridMapRect usingAnnotations:filteredAnnotationsInBucket];
[filteredAnnotationsInBucket removeObject:annotationForGrid];
annotationForGrid.containedAnnotations = [filteredAnnotationsInBucket allObjects];
[self.mapView addAnnotation:annotationForGrid];
//force reload of the image because it's not done if annotationForGrid is already present in the bucket!!
MKAnnotationView* annotationView = [self.mapView viewForAnnotation:annotationForGrid];
NSString *imageName = [AnnotationsViewUtils imageNameForItem:annotationForGrid selected:NO];
UILabel *countLabel = [[UILabel alloc] initWithFrame:CGRectMake(15, 2, 8, 8)];
[countLabel setFont:[UIFont fontWithName:POINT_FONT_NAME size:10]];
[countLabel setTextColor:[UIColor whiteColor]];
[annotationView addSubview:countLabel];
imageName = [AnnotationsViewUtils imageNameForItem:annotationForGrid selected:NO];
annotationView.image = [UIImage imageNamed:imageName];
if (filteredAnnotationsInBucket.count > 0){
[self.mapView deselectAnnotation:annotationForGrid animated:NO];
}
for (PointMapItem *annotation in filteredAnnotationsInBucket) {
[self.mapView deselectAnnotation:annotation animated:NO];
annotation.clusterAnnotation = annotationForGrid;
annotation.containedAnnotations = nil;
if ([visibleAnnotationsInBucket containsObject:annotation]) {
CLLocationCoordinate2D actualCoordinate = annotation.coordinate;
[UIView animateWithDuration:0.3 animations:^{
annotation.coordinate = annotation.clusterAnnotation.coordinate;
} completion:^(BOOL finished) {
annotation.coordinate = actualCoordinate;
[self.mapView removeAnnotation:annotation];
}];
}
}
}
gridMapRect.origin.x += gridSize;
}
gridMapRect.origin.y += gridSize;
}
}
- (id<MKAnnotation>)annotationInGrid:(MKMapRect)gridMapRect usingAnnotations:(NSSet *)annotations {
NSSet *visibleAnnotationsInBucket = [self.mapView annotationsInMapRect:gridMapRect];
NSSet *annotationsForGridSet = [annotations objectsPassingTest:^BOOL(id obj, BOOL *stop) {
BOOL returnValue = ([visibleAnnotationsInBucket containsObject:obj]);
if (returnValue) {
*stop = YES;
}
return returnValue;
}];
if (annotationsForGridSet.count != 0) {
return [annotationsForGridSet anyObject];
}
MKMapPoint centerMapPoint = MKMapPointMake(MKMapRectGetMinX(gridMapRect), MKMapRectGetMidY(gridMapRect));
NSArray *sortedAnnotations = [[annotations allObjects] sortedArrayUsingComparator:^(id obj1, id obj2) {
MKMapPoint mapPoint1 = MKMapPointForCoordinate(((id<MKAnnotation>)obj1).coordinate);
MKMapPoint mapPoint2 = MKMapPointForCoordinate(((id<MKAnnotation>)obj2).coordinate);
CLLocationDistance distance1 = MKMetersBetweenMapPoints(mapPoint1, centerMapPoint);
CLLocationDistance distance2 = MKMetersBetweenMapPoints(mapPoint2, centerMapPoint);
if (distance1 < distance2) {
return NSOrderedAscending;
}
else if (distance1 > distance2) {
return NSOrderedDescending;
}
return NSOrderedSame;
}];
return [sortedAnnotations objectAtIndex:0];
}
Both should work fine, but if you have any question, feel free to ask!
After long hours of research, I have finally found a wonderful guy who did this.
Thanks a lot to you DDRBoxman.
Check his github : https://github.com/DDRBoxman/google-maps-ios-utils
He recently pushed some code sample.
When I wanted to run his project, I had some issues. I just deleted the Google Maps SDK and follow the complete Google tutorial to integrate Google Maps SDK. Then, no more issues, I was able to run the app.
Don't forget to put your API KEY in the AppDelegate.m.
I will work with this lib for the following days, I will let you know if I find some bugs.
EDIT #1 : I worked a lot on clusters these days. My final approach is to integrate an MKMapView, create the cluster on an MKMapView (way much easier than doing it on the Google Maps SDK for iOS) and integrate Google Maps Places to my iOS project.
The performance are better with this approach than the previous one.
EDIT #2 : I don't know if you use Realm or if you plan to use it but they provide a really good solution for map clustering : https://realm.io/news/building-an-ios-clustered-map-view-in-objective-c/
i have an app handle this issue, below is the code
loop all markers (nsdictionary) in an array
use gmsmapview.projection to get CGPoint in order to find out whether the marker should group together
3 i use 100 points to test and the response time is quite satisfied.
4 the map will redraw if the zoom level difference is over 0.5;
-(float)distance :(CGPoint)pointA point:(CGPoint) pointB{
return sqrt( (pow((pointA.x - pointB.x),2) + pow((pointA.y-pointB.y),2)));
}
-(void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position{
float currentZoomLevel = mapView.camera.zoom;
if (fabs(currentZoomLevel- lastZoomLevel_)>0.5){
lastZoomLevel_ = currentZoomLevel;
markersGroupArray_ = [[NSMutableArray alloc] init];
for (NSDictionary *photo in photoArray_){
float coordx = [[photo objectForKey:#"coordx"]floatValue];
float coordy = [[photo objectForKey:#"coordy"] floatValue];
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(coordx, coordy);
CGPoint currentPoint = [mapView.projection pointForCoordinate:coord];
if ([markersGroupArray_ count] == 0){
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:photo, nil];
[markersGroupArray_ addObject:array];
}
else{
bool flag_groupadded = false;
int counter= 0;
for (NSMutableArray *array in markersGroupArray_){
for (NSDictionary *marker in array){
float mcoordx = [[marker objectForKey:#"coordx"]floatValue];
float mcoordy = [[marker objectForKey:#"coordy"]floatValue];
CLLocationCoordinate2D mcoord = CLLocationCoordinate2DMake(mcoordx, mcoordy);
CGPoint mpt = [mapView.projection pointForCoordinate:mcoord];
if ([self distance:mpt point:currentPoint] <30){
flag_groupadded = YES;
break;
}
}
if (flag_groupadded){
break;
}
counter++;
}
if (flag_groupadded){
if ([markersGroupArray_ count]>counter){
NSMutableArray *groupArray = [markersGroupArray_ objectAtIndex:counter];
[groupArray insertObject:photo atIndex:0];
[markersGroupArray_ replaceObjectAtIndex:counter withObject:groupArray];
}
}
else if (!flag_groupadded){
NSMutableArray * array = [[NSMutableArray alloc]initWithObjects:photo, nil];
[markersGroupArray_ addObject:array];
}
}
} // for loop for photoArray
// display group point
[mapView clear];
photoMarkers_ = [[NSMutableArray alloc] init];
for (NSArray *array in markersGroupArray_){
NSLog(#"arry count %d",[array count]);
NSDictionary *item = [array objectAtIndex:0];
float coordx = [[item objectForKey:#"coordx"]floatValue];
float coordy = [[item objectForKey:#"coordy"] floatValue];
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(coordx, coordy);
GMSMarker *marker = [[GMSMarker alloc] init];
marker.position = coord;
marker.map = mapView;
[photoMarkers_ addObject:marker];
marker = nil;
}
NSLog(#"markers %#",photoMarkers_);
} // zoomlevel diffference thersold
}
I'm trying to use the project MosaicUI in my project,
the problem I'm facing is how to move from loading local images from a json file, to images from my web server ?
Did anyone tried this before may be? Thanks.
You can use AFNetWorking to load images from Websites.
Change the method setModule in MosaicDataView.m like this
-(void)setModule:(MosaicData *)newModule
{
module = newModule;
__weak UIImageView *weakImage = imageView;
__weak UIView *weakView = self;
__weak MosaicData *weakModule = module;
UIImage *image = [imageView loadImage:module.tg_id ofType:#"png"];
if (image)
{
[imageView setImage:image];
}
else
{
[imageView setImageWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:module.l_cover_url]] placeholderImage:[UIImage imageNamed:#"test.png"] success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image)
{
[weakImage setImage:image];
CGSize imgFinalSize = CGSizeZero;
[weakImage saveImage:image withFileName:weakModule.tg_id ofType:#"png"];
if (image.size.width < image.size.height)
{
imgFinalSize.width = weakView.bounds.size.width;
imgFinalSize.height = weakView.bounds.size.width * image.size.height / image.size.width;
// This is to avoid black bars on the bottom and top of the image
// Happens when images have its height lesser than its bounds
if (imgFinalSize.height < weakView.bounds.size.height)
{
imgFinalSize.width = weakView.bounds.size.height * weakView.bounds.size.width / imgFinalSize.height;
imgFinalSize.height = weakView.bounds.size.height;
}
}
else
{
imgFinalSize.height = weakView.bounds.size.height;
imgFinalSize.width = weakView.bounds.size.height * image.size.width / image.size.height;
// This is to avoid black bars on the left and right of the image
// Happens when images have its width lesser than its bounds
if (imgFinalSize.width < weakView.bounds.size.width)
{
imgFinalSize.height = weakView.bounds.size.height * weakView.bounds.size.width / imgFinalSize.height;
imgFinalSize.width = weakView.bounds.size.width;
}
}
weakImage.frame = CGRectMake(0, 0, imgFinalSize.width, imgFinalSize.height);
weakImage.center = CGPointMake(weakView.frame.size.width/2, weakView.frame.size.height/2);
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error)
{
NSLog(#"%#",error);
}];
}
// Set new title
NSInteger marginLeft = self.frame.size.width / 20;
NSInteger marginBottom = self.frame.size.height / 20;
titleLabel.text = module.title;
titleLabel.font = [self fontWithModuleSize];
CGSize newSize = [module.title sizeWithFont:titleLabel.font constrainedToSize:titleLabel.frame.size];
CGRect newRect = CGRectMake(10+marginLeft,
self.frame.size.height - newSize.height - marginBottom,
newSize.width-30,
newSize.height);
titleLabel.frame = newRect;
}
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?
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"
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()
}
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
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.
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
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];