MKOverlays Combining Image Tiles And KML - ios

I am trying to display tiled images with a kml overlay on top of the tiled images (code below) and am receiving the following error:
'NSInvalidArgumentException', reason: '-[MKPolyline tilesInMapRect:zoomScale:]: unrecognized selector sent to instance
Does anyone have any suggestions as to whether or not I am approaching the multiple overlays correctly or why I am getting this error?
Thanks in advance!
(void)viewDidLoad
{
[super viewDidLoad];
// Initialize the map overlay with tiles in the app's bundle.
NSString *tileDirectory = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Tiles"];
MapOverlay *overlay1 = [[MapOverlay alloc] initWithDirectory:tileDirectory];
// Locate the path to the route.kml file in the application's bundle
// and parse it with the KMLParser.
NSString *path = [[NSBundle mainBundle] pathForResource:#"route" ofType:#"kml"];
NSURL *url = [NSURL fileURLWithPath:path];
kmlParser = [[KMLParser alloc] initWithURL:url];
[kmlParser parseKML];
// Add all of the MKOverlay objects parsed from the KML file to the map.
NSArray *overlay2 = [kmlParser overlays];
[map addOverlay:overlay1];
[map addOverlays:overlay2];
// Set the starting location.
CLLocationCoordinate2D startingLocation;
startingLocation.latitude = 0.00;
startingLocation.longitude =-0.00;
map.region = MKCoordinateRegionMakeWithDistance(startingLocation, 4600, 4600);
[map setCenterCoordinate:startingLocation];
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay1
{
MapOverlayView *view = [[MapOverlayView alloc] initWithOverlay:overlay1];
view.overlayAlpha = 1.0;
return view;
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay2:(id <MKOverlay>)overlay2
{
return [kmlParser viewForOverlay:overlay2];
}
#end

At least the viewForOverlay delegate method is not implemented correctly.
You've written two methods: mapView:viewForOverlay: and mapView:viewForOverlay2:.
But the map view will only always call mapView:viewForOverlay: since that is the method name defined by the MKMapViewDelegate protocol.
The mapView:viewForOverlay2: method will be ignored and not called by the map view.
So what happens is that when the overlay2 overlay array is added to the map, it calls the mapView:viewForOverlay: method which creates a MapOverlayView for the overlay (instead of getting the overlay view from kmlParser). This may cause issues (maybe MapOverlayView only handles MapOverlay-type overlays).
All overlays should be handled in the mapView:viewForOverlay: method.
To handle multiple types of overlays, check the overlay class and handle accordingly:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MapOverlay class]])
{
MapOverlayView *view = [[MapOverlayView alloc] initWithOverlay:overlay];
view.overlayAlpha = 1.0;
return view;
}
//if not a MapOverlay, get from kmlParser...
return [kmlParser viewForOverlay:overlay];
}
Remove the mapView:viewForOverlay2: method.

Related

MKPinAnnotationView pinColor

I can't figure out why a MKPinAnnotationView associated (in theory) with a MKPointAnnotation doesn't appear on the map. In fact, the pin appears but it isn't purple as it should be...
Here is the code:
MKPointAnnotation *myPersonalAnnotation= [[MKPointAnnotation alloc]init];
myPersonalAnnotation.title= [appDelegate.theDictionary objectForKey:#"theKey"];
myPersonalAnnotation.coordinate=CLLocationCoordinate2DMake(6.14, 10.7);
MKPinAnnotationView *myPersonalView=[[MKPinAnnotationView alloc] initWithAnnotation:myPersonalAnnotation reuseIdentifier:#"hello"];
myPersonalView.pinColor=MKPinAnnotationColorPurple;
[myMap addAnnotation:myPersonalAnnotation];
If you want to create an annotation view different from the default red pin, you have to create and return it in the map view's viewForAnnotation delegate method.
The map will automatically call the viewForAnnotation delegate method whenever it needs to show some annotation (either the built-in user location or annotations you add).
Remove the local creation of myPersonalView from before the call to addAnnotation and implement the viewForAnnotation method instead.
For example:
//in your current method...
MKPointAnnotation *myPersonalAnnotation= [[MKPointAnnotation alloc]init];
myPersonalAnnotation.title= [appDelegate.theDictionary objectForKey:#"theKey"];
myPersonalAnnotation.coordinate=CLLocationCoordinate2DMake(6.14, 10.7);
[myMap addAnnotation:myPersonalAnnotation];
//...
//add the viewForAnnotation delegate method...
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
//if annotation is the user location, return nil to get default blue-dot...
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
//create purple pin view for all other annotations...
static NSString *reuseId = #"hello";
MKPinAnnotationView *myPersonalView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (myPersonalView == nil)
{
myPersonalView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
myPersonalView.pinColor = MKPinAnnotationColorPurple;
myPersonalView.canShowCallout = YES;
}
else
{
//if re-using view from another annotation, point view to current annotation...
myPersonalView.annotation = annotation;
}
return myPersonalView;
}
Make sure the map view's delegate property is set otherwise the delegate method won't get called.
In code, use myMap.delegate = self; (eg. in viewDidLoad) or make the connection in Interface Builder if myMap is an IBOutlet.

Remove a polyLine from the mapView

I've read many posts about it and still i have a problem.
This is my code to draw a polyLine between two points:
-(void) drawAline:(CLLocation*)newLocation
{
//drawing a line
CLLocationCoordinate2D coordinateArray[2];
coordinateArray[0] = CLLocationCoordinate2DMake(newLocation.coordinate.latitude, newLocation.coordinate.longitude);
coordinateArray[1] = CLLocationCoordinate2DMake(self.jerusalem.coordinate.latitude, self.jerusalem.coordinate.longitude);
self.routeLine = [MKPolyline polylineWithCoordinates:coordinateArray count:2];
[self.mapView setVisibleMapRect:[self.routeLine boundingMapRect]];
[self.mapView addOverlay:self.routeLine];
}
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
if(overlay == self.routeLine)
{
if(nil == self.routeLineView)
{
self.routeLineView = [[MKPolylineView alloc] initWithPolyline:self.routeLine];
self.routeLineView.fillColor = [UIColor blueColor];
self.routeLineView.strokeColor = [UIColor blueColor];
self.routeLineView.lineWidth = 5;
}
return self.routeLineView;
}
return nil;
}
thats works fine.
The problem is to remove the line.
The next code doesn't work:
for (id<MKOverlay> overlayToRemove in self.mapView.overlays)
{
if ([overlayToRemove isKindOfClass:[MKPolylineView class]])
{
[mapView removeOverlay:overlayToRemove];
}
}
the next code doesn't work neither:
if (self.routeLine)
{
[self.mapView removeOverlay:self.routeLine];
self.routeLineView = nil;
self.routeLine = nil;
}
Thanks!
In the code that loops through the map view's overlays array, this line is the problem:
if ([overlayToRemove isKindOfClass:[MKPolylineView class]])
The map view's overlays array contains objects of type id<MKOverlay> (the for-loop correctly declares overlayToRemove as such).
So the overlays array contains the model objects for the overlays and not the views.
The MKPolylineView class is the view for an MKPolyline overlay model.
So the if condition should be:
if ([overlayToRemove isKindOfClass:[MKPolyline class]])
Note that such a loop will remove all polylines from the map. If you wanted to delete specific polylines, you could set the title on each one when adding it and then check it before removing.
The second piece of code that checks and deletes self.routeLine directly should work as long as self.routeLine is not nil and contains a valid reference to an overlay currently on the map.
If you have only a single overlay on the map (the one polyline), you could also just call removeOverlays to delete all overlays from the map (whatever they are):
[self.mapView removeOverlays:self.mapView.overlays];
Your overlay is a MKPolyline the MKPolylineView is just how the overlay is displayed when the map is zoomed or scrolled so that the overlay's data shows in the window. The map view's overlays array contains the data that will be used to generate the overlay views. There are no views in the overlays array. So, to make your code work, change this line
if ([overlayToRemove isKindOfClass:[MKPolylineView class]])
to
if ([overlayToRemove isKindOfClass:[MKPolyline class]])
in your third snippet and you will be fine

viewForOverlay method not being called while parsing KML

I'm using KML to show some overlays in map view. After parsing KML file I added overlay to my map view, but the map view delegate method viewForOverlay is not being called even if I set delegate property to map view.
What are the possible reasons for this problem?
NSURL *url = [NSURL fileURLWithPath:filePath];
kmlParser = [[KMLParser alloc] initWithURL:url];
[kmlParser parseKML];
// Add all of the MKOverlay objects parsed from the KML file to the map.
NSArray *overlays = [kmlParser overlays];
[self.mapView addOverlays:overlays];
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
return [kmlParser viewForOverlay:overlay];
}
I have done it using KMLViewer example.

removeOverlay:overlay not working

I'm a new guy in the XCode realm and was wondering if someone could help me out.
Essentially, I'm playing around with WWDC2010's TileMap project example and am trying to figure out a way to hide their NOAA chart using a segmented controller.
I can activate the overlay and it displays fine, but I can't for the life of me remove it using a segmented controller.
Here's some code from the header file:
#interface ViewController : UIViewController <MKMapViewDelegate> {
IBOutlet MKMapView *map;
IBOutlet UISegmentedControl *controller;
}
- (IBAction)switchMap:(id)sender;
#end
and here's the code for the .m:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"initial view loaded");
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay {
TileOverlayView *view = [[TileOverlayView alloc] initWithOverlay:overlay];
view.tileAlpha = 1;
return view;
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (IBAction)switchMap:(id)overlay {
if (controller.selectedSegmentIndex == 0) {
NSLog(#"welp... it loaded...");
[map removeOverlay:overlay];
}
if (controller.selectedSegmentIndex == 1) {
NSLog(#"Map Overlay works");
NSString *tileDirectory = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Tiles"];
TileOverlay *overlay = [[TileOverlay alloc] initWithTileDirectory:tileDirectory];
[map addOverlay:overlay];
MKMapRect visibleRect = [map mapRectThatFits:overlay.boundingMapRect];
visibleRect.size.width /= 2;
visibleRect.size.height /= 2;
visibleRect.origin.x += visibleRect.size.width / 2;
visibleRect.origin.y += visibleRect.size.height / 2;
map.visibleMapRect = visibleRect;
}
if (controller.selectedSegmentIndex == 2) {
NSLog(#"But... overlay isnt hiding waa");
NSString *tileDirectory = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Tiles"];
TileOverlay *overlay = [[TileOverlay alloc] initWithTileDirectory:tileDirectory];
[map removeOverlay:overlay]; }
}
In a control action method, the first parameter (no matter what you name it) is always the object that called the method.
Here, the control is a UISegmentedControl so the parameter that gets passed to switchMap: is a reference to that control. In the .h you've declared the parameter with the name sender but in the .m it's named overlay.
Regardless of the name, it's still the segmented control object so passing it to removeOverlay is meaningless and will do nothing.
So in this code:
if (controller.selectedSegmentIndex == 0) {
NSLog(#"welp... it loaded...");
[map removeOverlay:overlay];
}
overlay is pointing to the segmented control and so the removeOverlay does nothing.
In this code:
if (controller.selectedSegmentIndex == 2) {
NSLog(#"But... overlay isnt hiding waa");
NSString *tileDirectory = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Tiles"];
TileOverlay *overlay = [[TileOverlay alloc] initWithTileDirectory:tileDirectory];
[map removeOverlay:overlay]; }
You are creating a new local overlay object (the compiler is also probably giving you a warning about a local variable hiding the parameter). This new object is separate from the overlay that was already added to the map. Calling removeOverlay on this new object does nothing because this new instance was never added to the map in the first place.
To remove an existing overlay, you either have to keep an ivar reference to it when you add it and pass that ivar to remove or find it in the map view's overlays array.
However, if you will ever have only one overlay, you can pass the first object in the map view's overlays array or just call removeOverlays (plural) and pass the whole array:
if (map.overlays.count > 0)
[map removeOverlay:[map.overlays objectAtIndex:0]];
//OR...
if (map.overlays.count > 0)
[map removeOverlays:map.overlays];

MKAnnotation not getting selected in iOS5

My app places a pushpin on the map and then selects its using animation so the user has a visual clue and can immediately read the title/subtitle. The following code works in both iOS4 and iOS5, but in iOS5, the annotation doesn't get selected automatically unless I change the animation to NO in the selectAnnotation method.
Any ideas why?
MapAnnotations *pushpin = [[MapAnnotations alloc] initWithCoordinate:coordinate];
pushpin.title = [selectedStation valueForKey:#"name"];
pushpin.subtitle = [selectedStation valueForKey:#"address"];
[stationMap addAnnotation:pushpin];
[stationMap selectAnnotation:pushpin animated:YES];
[pushpin release]; pushpin = nil;
Not sure why it would work before but the animation probably requires the annotation view to be created and ready which is unlikely immediately after adding the annotation.
What you can do is move the selection to the didAddAnnotationViews delegate method which should work on all iOS versions:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
for (MKAnnotationView *av in views) {
if ([av.annotation isKindOfClass:[MapAnnotations class]]) {
MapAnnotations *pushpin = (MapAnnotations *)av.annotation;
if (_this_pushpin_is_the_one_to_select) {
[mapView selectAnnotation:av.annotation animated:YES];
break; //or return;
}
}
}
}

Resources