I have only a couple of months experience on Objective-C.
I have a code to search a list of customer address, then show the annotation of these addresses. As the customers cover many regions, I would like to make map view's center and span adapt to the results.
Therefore, I planed to record all searched place marks and calculate an average latitude and longitude. Below is my code
self.mapView.mapType = MKMapTypeStandard;
for (Customer *customer in customers) {
CLGeocoder *geoCoder = [[CLGeocoder alloc] init];
NSString *customerAddress = [Customer getWholeAddressOfCustomer:customer];
NSString *color = #"";
if([customer.status isEqualToString:#"1"])
{
color = #"Green";
}else if ([customer.status isEqualToString:#"2"]) {
color = #"Yellow";
}else if ([customer.status isEqualToString:#"3"])
{
color = #"Red";
}else {
color = customer.status;
}
[geoCoder geocodeAddressString:customerAddress completionHandler:^(NSArray *placemarks, NSError *error)
{
[NSThread sleepForTimeInterval:0.25];
if (placemarks.count == 0)
{
NSLog(#"No place for customer %# was found",customerAddress);
}
CLPlacemark *placemark = [placemarks objectAtIndex:0];
if(placemark.location.coordinate.latitude != 0.000000 && placemark.location.coordinate.longitude != 0.000000)
{
CustomerAnnotation *annotation = [[CustomerAnnotation alloc]initWithCoordinate:placemark.location.coordinate andName:customer.name andNumber:customer.customerNo andColor:color];
[self.mapAnnotations addObject:annotation];
}
}];
}
CLLocationDegrees totalLatitude=0;
CLLocationDegrees totalLongitude = 0;
for (int i=0; i < [self.mapAnnotations count]; i++) {
totalLatitude += [(CustomerAnnotation *)[self.mapAnnotations objectAtIndex:i] coordinate].latitude;
totalLongitude += [(CustomerAnnotation *)[self.mapAnnotations objectAtIndex:i] coordinate].longitude;
[self.mapView addAnnotation:[self.mapAnnotations objectAtIndex:i]];
}
MKCoordinateRegion focusRegion;
focusRegion.center.latitude = totalLatitude/[self.mapAnnotations count];
focusRegion.center.longitude = totalLongitude/[self.mapAnnotations count];
focusRegion.span.latitudeDelta = 20; //will modify this parameter later to self adapt the map
focusRegion.span.longitudeDelta = 20;
[self.mapView setRegion:focusRegion animated:YES];
But the problem is the timing issue like This problem
The setRegion is executed prior to obtaining those place marks. In the solution raised of that link, I should call a method in the completion block. But this solution is not suitable to a multiple address issue as I need to add all place marks to my method before I setRegion.
Is there anyway to make a method be executed after obtaining all results from geocodeAddressString?
Thanks in advance.
Issue resolved.
Outside of the block, I used a counter to record the annotation added.
__block int customerCount = 0;
When this count equals to the whole number, auto focus the central area of all customer.
if (customerCount == [customers count]) {
//Set the focus
}
Related
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've been attempting to write some logic for a program I have been working on and have run into some difficulty.
Essentially what I'm creating on my stage programmatically is a 4x4 grid (16 blocks), which looks just like this:
The user's task is to plot a contiguous shape onto the grid by clicking on the blocks and their shape should feature no gaps and no diagonally plotted blocks, for example the following would be a legal shape:
However, the following shape wouldn't be and would throw out an error to the user in the form of a pop-up graphic:
The plotting process for the grid is associated with a 4x4 virtual representation of the grid in Boolean Array form and looks like this:
public static var ppnRowArray1:Array = [false,false,false,false];
public static var ppnRowArray2:Array = [false,false,false,false];
public static var ppnRowArray3:Array = [false,false,false,false];
public static var ppnRowArray4:Array = [false,false,false,false];
public static var ppnColumnArray:Array = [ppnRowArray1,ppnRowArray2,ppnRowArray3,ppnRowArray4];
As the user clicks and selects a block, changing the colour property to brown, the relevant boolean property in my 'virtual grid representation' array will change from false to true. If a plot is illegally made then this property is changed back to false and the user is then invited to try their next plot again.
I have managed to write the code which forces the user to plot a legal shape and works out when an illegal plot has been made, but I now need to write the logic for when a user de-selects a block from an existing legal shape, making it non-contiguous and this is where my problem lies.
Here is the working solution as it stands.
//---------------------------------------------------------------------
public static function ppnCountSetCells():int
{
//Count Each 4x4 Grid Cell
var count:int = 0;
for (var row=0; row<=3; row++)
{
for (var col=0; col<=3; col++)
{
if (ppnColumnArray[col][row])
{
count++;
}
}
}
return count;
}
//---------------------------------------------------------------------
public static function ppnBlockValid():Boolean
{
if (ppnCountSetCells() > 1)
{
for (var row=0; row<=3; row++)
{
for (var col=0; col<=3; col++)
{
if (ppnColumnArray[col][row] == true)
{
// Check if we are connected to another set square
var validNeighbours:int = 0;
// Check North
if (row > 0)
{
if (ppnColumnArray[col][row - 1] == true)
{
validNeighbours++;
}
}
// Check South
if (row < 3)
{
if (ppnColumnArray[col][row + 1] == true)
{
validNeighbours++;
}
}
// Check West
if (col > 0)
{
if (ppnColumnArray[col - 1][row] == true)
{
validNeighbours++;
}
}
//-----------------------------------------------------------------------------------------
// Check East
if (col < 3)
{
if (ppnColumnArray[col + 1][row] == true)
{
validNeighbours++;
}
}
//-----------------------------------------------------------------------
if (validNeighbours < 1)
{
return false;
}
//-----------------------------------------------------------------------
}
}
}
}
return true;
}
//---------------------------------------------------------------------
function addBlock(e:MouseEvent):void
{
//trace("You Have Clicked On Grid Block Number: " + e.currentTarget.id);
if (InterfaceButtons.panelOpen == false)
{
//Listen to see if the block click is adjoining and pass back to see if it is valid on the grid
var col:int = (e.currentTarget.id - 1) % 4;
var row:int = (e.currentTarget.id - 1) / 4;
ppnColumnArray[col][row] = true;
addOrRemove = "add";
ppnBlockValid();
//Get the Block Valid Result (True or False) and pass it into a Boolean variable to use later
ppnGridError = ppnBlockValid();
trace("Is This Valid? " + ppnBlockValid());
//----------------------------------------------------------------------------------------------
//Push Blocks Selected into Array
ppnShapeArray[e.currentTarget.id] = true;
trace(ppnShapeArray);
//----------------------------------------------------------------------------------------------
//Add 1 to the block count which directly effects the final outcome depending on ++ or --
ppnBlocksSelected++;
PlantPopNitDesignPlot.ppnPlotMade = false;
//Hide Block to Reveal Brown One
e.currentTarget.alpha = 0;
//-----------------------------------------------------------------------------------------------
//Output an error if one is present on Click based on gridError Boolean Variable
ppnOutputAddError();
if (ppnGridError == false)
{
//Restore the block's alpha property as it isn't allowed to be selected, removing counter by one -- and changing final output accordingly
e.currentTarget.alpha = 1;
ppnBlocksSelected--;
ppnColumnArray[col][row] = false;
ppnShapeArray[e.currentTarget.id] = false;
ppnPopulateTotalSiteUnitsTxt();
}
//Update final total
ppnPopulateTotalSiteUnitsTxt();
//Call again to do dynamic colour font change should total exceed 10
ppnPopulateTotalSiteUnitsTxt();
//Added in to make sure it executes every time if an error is made.
if (ppnGridError == true)
{
e.currentTarget.removeEventListener(MouseEvent.CLICK, addBlock);
e.currentTarget.addEventListener(MouseEvent.CLICK, removeBlock);
}
}
}
function removeBlock(e:MouseEvent):void
{
if (InterfaceButtons.panelOpen == false)
{
var col:int = (e.currentTarget.id - 1) % 4;
var row:int = (e.currentTarget.id - 1) / 4;
ppnColumnArray[col][row] = false;
addOrRemove = "remove";
ppnBlockValid();
ppnGridError = ppnBlockValid();
trace("Is This Removal Valid? " + ppnBlockValid());
//trace("You Have Clicked On Grid Block Number: " + e.currentTarget.id);
e.currentTarget.alpha = 1;
ppnShapeArray[e.currentTarget.id] = false;
//trace("ppnShapeArray - " + ppnShapeArray);
//---------------------------------------------------------------------
ppnBlocksSelected--;
PlantPopNitDesignPlot.ppnPlotMade = false;
//Output an error if one is present on Click based on gridError Boolean Variable
ppnOutputRemoveError();
if (ppnGridError == false)
{
//Restore the block's alpha property as it isn't allowed to be selected, removing counter by one -- and changing final output accordingly
e.currentTarget.alpha = 0;
ppnBlocksSelected--;
ppnColumnArray[col][row] = true;
ppnShapeArray[e.currentTarget.id] = true;
ppnPopulateTotalSiteUnitsTxt();
}
//Update Final Total
ppnPopulateTotalSiteUnitsTxt();
//Call again to do dynamic colour font change should total falls below 10
ppnPopulateTotalSiteUnitsTxt();
//Added in to make sure it executes every time.
if (ppnGridError == true)
{
e.currentTarget.addEventListener(MouseEvent.CLICK, addBlock);
e.currentTarget.removeEventListener(MouseEvent.CLICK, removeBlock);
}
}
}
}
}
//---------------------------------------------------------------------
Now, for most occurences this logic works and detects illegal plots when adding or removing a block to the shape, however recently I discovered that when I have 5 > blocks in the shape, the logic for detecting an error on removal fails in certain circumstances.
A few examples of shapes being declared true and legal when they are not (when a block has been removed) are as follows:
I can see that it is the logic written in my 'ppnBlockValid():Boolean' function that needs adjusting to compensate for these outputs. It seems you can only remove a block providing that the neighbouring blocks are still joined to something else. While this works for smaller shapes, larger shapes (e.g. 5 blocks or more) can theoretically be split down the middle, so I think the code needs adjusting to account for this.
But how? Any help on this would be greatly appreciated.
Many thanks in advance and if you need any further information from me please let me know.
Cheers,
Joel
Edited
Thank you very much for providing that enhanced code and explanation #dhc I really appreciate it, but I'm still a little confused about how to implement all this properly.
Here is my current 'ppnBlockValid' function code based on your suggestion below:
public static function ppnBlockValid():Boolean
{
ppnIslands = [];
ppnAddNewIsland = [];
ppnAddToExistingIsland = [];
if (ppnCountSetCells() > 1)
{
for (var row=0; row<=3; row++)
{
for (var col=0; col<=3; col++)
{
if (ppnColumnArray[col][row] == true)
{
var addedToIsland = false;
// Check if we are connected to another set square
var validNeighbours:int = 0;
// Check North
if (row > 0)
{
if (ppnColumnArray[col][row - 1] == true)
{
validNeighbours++;
}
//----------------------------------
//ISLAND CHECK
if (ppnColumnArray[col][row - 1])
{
ppnAddToExistingIsland.push([col,row - 1],[col,row]);
addedToIsland = true;
}
}
// Check South
if (row < 3)
{
if (ppnColumnArray[col][row + 1] == true)
{
validNeighbours++;
}
}
// Check West
if (col > 0)
{
if (ppnColumnArray[col - 1][row] == true)
{
validNeighbours++;
}
//----------------------------------
//ISLAND CHECK
if (ppnColumnArray[col - 1][row])
{
ppnAddToExistingIsland.push([col - 1,row],[col,row]);
addedToIsland = true;
}
}
//-----------------------------------------------------------------------------------------
// Check East
if (col < 3)
{
if (ppnColumnArray[col + 1][row] == true)
{
validNeighbours++;
}
}
//-----------------------------------------------------------------------
if (! addedToIsland)
{
ppnIslands.push([col,row]);
}
//-----------------------------------------------------------------------
if (ppnIslands.length >= 2 && addOrRemove == "remove")
{
trace("TWO ISLANDS HAVE BEEN FORMED AND AN ERROR SHOULD BE OUTPUT");
validNeighbours--;
}
/**/
//return (ppnIslands.length<=1);// 0 islands is valid also!
//-----------------------------------------------------------------------
if (validNeighbours < 1)
{
return false;
}
//-----------------------------------------------------------------------
}
}
}
}
return true;
}
//---------------------------------------------------------------------
I have been using the following shape as my experiment with the code:
Based on the above code and example shape, my current trace output is:
ppnIsland = |0,0|,0,2
ppnAddNewIsland =
ppnAddToExistingIsland = 0,0, 1,0, 1,0, 2,0, 2,0, 3,0, 1,0, 1,1, 1,1, 1,2, 0,2, 1,2, 1,2, 2,2, 2,2, 3,2
It appears that despite the shape being contiguous, the code I've tried interpreting from you is finding an additional island, in this case 'Col: 0, Row: 2', before a block removal has even taken place? Is this right?
This code of course does output an error if I try to remove the middle (red) block as the 'ppnIsland' array contains > 1 island, however I don't think its detecting the correct output?
Do I need to cross-reference the 'ppnIsland' and 'ppnAddToExistingIsland' arrays using the indexOf command to check if either element is part of an existing island?
Joel
You could track "islands" as separate lists as you process (in ppnBlockValid). So, for instance (caps are selected tiles):
a B c d
e F g H
i J k L
m n o p
When you process row one, you create an island for B. When you process row 2, you find B connected to F, so the island becomes [B,F]. Then, when you encounter H, which is not connected, you have two islands: [B,F],[H]. On row 3, your islands would look like [B,F,J],[H,L]. If K were selected, you'd find J and L connected, and you'd consolidate them into one island. You only have to check the current row for connections between islands, so you'd only have to check J and L in this case.
If you end up with one island, you're fine, otherwise not. This is, unfortunately, an order n**2 search, but your grid is small so it probably doesn't mean much.
Something like this (WARNING: not debugged code):
private var islands : Array;
public function ppnBlockValid():Boolean {
islands = [];
if (ppnCountSetCells() > 1) {
for (var row=0; row<=3; row++) {
for (var col=0; col<=3; col++) {
if (ppnColumnArray[col][row]) {
var addedToIsland = false;
// Check if we are connected to another set square
// Check North
if (row > 0) {
if (ppnColumnArray[col][row-1]) {
addToExistingIsland([col,row-1],[col,row]);
addedToIsland = true;
}
}
// Check South - not needed since next row will catch this on North
// Check West
if (col > 0) {
if (ppnColumnArray[col-1][row]) {
addToExistingIsland([col-1,row],[col,row]);
addedToIsland = true;
}
}
// Check East - not needed since next col will catch this on West
if (!addedToIsland) { addNewIsland([col,row]); }
}
}
}
}
return (islands.length<=1); // 0 islands is valid also!
}
You just have to implement "addNewIsland" and "addToExistingIsland". addNewIsland is easy: just add a new element to the islands array. You may want to use a string ID for the tiles instead of an array (e.g. "01" instead of [0,1]) since it may make checking for existing elements easier (e.g. you can use indexOf to find a tile in an island).
addToExistingIsland is a bit trickier: you have to check if either element is part of an existing island, and consolidate those islands if so. If not, the new tile just gets appended to the island.
You don't need to maintain 3 arrays, just one island array that, at the end of ppnBlockValid, will either be length > 1 (invalid), or less (valid).
You cannot just populate the "ppnAddNewIsland" and "ppnAddToExistingIsland" arrays-- these should be functions. If ppnIslands is a class variable, then your addNewIsland function may look something like this:
private function addNewIsland(who) {
ppnIslands.push([who]);
}
... the only optimization I'd probably make is to turn who (passed as an array [col,row]) into a character string, as I mentioned, to make finding whether a tile exists in an island easier.
Then, you need a function like:
private function addToExistingIsland( existing_island_tile, new_tile ) {
}
This function must:
1. check whether these two tiles already exist in the same island (just return if so)
2. check whether new_tile is already on an island (consolidate the two islands into one if so)
3. otherwise, just add new_tile to the island that existing_island_tile is a member of
The code I presented should work pretty much as-is-- although you could add back in the "validNeighbours" logic if you want a quick exit for isolated tiles.
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
}
Google Maps 1.2.0 GMSCoordinateBounds now contains a containsCoordinate method that I was intending to use for filtering markers that are not on the visibleRegion. Unfortunately, when you init a GMSCoordinateBounds, you get bounds that encompass your region or path.
So my question is : is it possible to see if a CLLocationCoordinate2D is within a GMSPath?
So I'm answering my own question. I just needed to use the pointForCoordinate method to see if the point was on the screen. Works perfectly.
for (int i = 0; i < self.visibleLocations.count; i++) {
Location *location = [self.visibleLocations objectAtIndex:i];
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake([location.lat floatValue], [location.lng floatValue]);
CGPoint markerPoint = [self.googleMap.projection pointForCoordinate:coordinate];
if (markerPoint.x >= 0 && markerPoint.y >= 0 && markerPoint.x <= self.googleMap.frame.size.width && markerPoint.y <= self.googleMap.frame.size.height) {
GMSMarker *marker = [GMSMarker markerWithPosition:coordinate];
marker.title = location.title;
marker.icon = [UIImage imageNamed:#"search_measle_small.png"];
marker.map = self.googleMap;
}
}
I want to create a set of new NSDictionaries with different names dependant upon input data. Is this possible? E.g.
for (int i = 0; i < [holidayDestination length]; i++) {
NSMutableDictionary *[NSString stringWithFormat:#"holidayDestination%d", i] = [[NSMutableDictionary alloc] init];
// Other code here...
}
Thanks in advance.
No, while Objective-C strives to be like Smalltalk, it's only a thin veneer over C, so you really can't do these kinds of cool "meta" tricks. What you can do instead is set up a mutable array and for each element at i create your mutable dictionary. Something like this:
NSMutableArray *holidayDestinations = [NSMutableArray new];
for (int i = 0; i < [holidayDestination length]; i++)
{
[holidayDestinations addObject:[NSMutableDictionary new]];
NSMutableDictionary *working = [holidayDestinations objectAtIndex:i];
// Add elements to "working"
}