How to add 'Maps' app link to each of my map annotations - iphone

There are a few tutorials and questions on this, but I'm not knowledgeable enough yet to understand how to implement them into my particular app. I get JSON annotation data from a URL and parse it and add each annotation in for loop. I want to add a link on each annotation to open Maps for directions.
Here's my ViewController.H
#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
#import <MapKit/MapKit.h>
//MAP Setup
#interface ViewController : UIViewController <MKMapViewDelegate>
//map setup
#property (weak, nonatomic) IBOutlet MKMapView *mapView;
#property (nonatomic, strong) NSMutableData *downloadData;
//- (IBAction)refreshTapped:(id)sender;
#end
and my ViewController.m
- (void)viewDidLoad
{
////////////////////////
//Connection to download JSON map info
////////////////////////
self.downloadData = [NSMutableData new];
NSURL *requestURL2 = [NSURL URLWithString:#"http:OMITTED"];
NSURLRequest *request = [NSURLRequest requestWithURL:requestURL2];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
//scroller
[scroller setScrollEnabled:YES];
[scroller setContentSize:CGSizeMake(320,900)];
[super viewDidLoad];
//Map
[self.mapView.userLocation addObserver:self
forKeyPath:#"location"
options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld)
context:nil];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.downloadData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
id parsed = [NSJSONSerialization JSONObjectWithData:_downloadData options:kNilOptions error:nil];
////////////////////////
//Iterating and adding annotations
////////////////////////
for (NSDictionary *pointInfo in parsed)
{
NSLog([pointInfo objectForKey:#"name"]);
double xCoord = [(NSNumber*)[pointInfo objectForKey:#"lat"] doubleValue];
double yCoord = [(NSNumber*)[pointInfo objectForKey:#"lon"] doubleValue];
CLLocationCoordinate2D coords = CLLocationCoordinate2DMake(xCoord, yCoord);
MKPointAnnotation *point = [MKPointAnnotation new];
point.coordinate = coords;
point.title = [pointInfo objectForKey:#"name"];
[self.mapView addAnnotation:point];// or whatever your map view's variable name is
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//centers map on user loc and then allows for movement of map without re-centering on userlocation check.
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([self.mapView showsUserLocation])
{
MKCoordinateRegion region;
region.center = self.mapView.userLocation.coordinate;
MKCoordinateSpan span;
span.latitudeDelta = .50; // Change these values to change the zoom
span.longitudeDelta = .50;
region.span = span;
[self.mapView setRegion:region animated:YES];
self.mapView.showsUserLocation = NO;}
}
- (void)dealloc
{
[self.mapView.userLocation removeObserver:self forKeyPath:#"location"];
[self.mapView removeFromSuperview]; // release crashes app
self.mapView = nil;
}
#end

Launching the Maps App of the Location Awareness Programming Guide says:
If you would prefer to display map information in the Maps app as opposed to your own app, you can launch Maps programmatically using one of two techniques:
In iOS 6 and later, use an MKMapItem object to open Maps.
In iOS 5 and earlier, create and open a specially formatted map URL as described in Apple URL Scheme Reference.
The preferred way to open the Maps app is to use the MKMapItem class. This class offers both the openMapsWithItems:launchOptions: class method and the openInMapsWithLaunchOptions: instance method for opening the app and displaying locations or directions.
For an example showing how to open the Maps app, see “Asking the Maps App to Display Directions.”
So, you should:
Make sure to define your view controller to be the delegate for your map view;
Write a viewForAnnotation that turns on canShowCallout and turns on the callout accessory view:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
MKAnnotationView* annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:#"MyCustomAnnotation"];
annotationView.canShowCallout = YES;
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
return annotationView;
}
Then write a calloutAccessoryControlTapped method that opens the maps as outlined above, based upon what versions of iOS you're supporting, e.g., for iOS 6:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
id <MKAnnotation> annotation = view.annotation;
CLLocationCoordinate2D coordinate = [annotation coordinate];
MKPlacemark *placemark = [[MKPlacemark alloc] initWithCoordinate:coordinate addressDictionary:nil];
MKMapItem *mapitem = [[MKMapItem alloc] initWithPlacemark:placemark];
mapitem.name = annotation.title;
[mapitem openInMapsWithLaunchOptions:nil];
}
I don't know what additional geographic information your have in your KML, but you can presumably fill in the addressDictionary as you see fit.
In answer to your follow-up question about how to use the addressDictionary parameter of the MKPlacemark initializer method, initWithCoordinate, if you had NSString variables for the street address, the city, the state, the zip, etc., it would look like:
NSDictionary *addressDictionary = #{(NSString *)kABPersonAddressStreetKey : street,
(NSString *)kABPersonAddressCityKey : city,
(NSString *)kABPersonAddressStateKey : state,
(NSString *)kABPersonAddressZIPKey : zip};
For this to work, you have to add the appropriate framework, AddressBook.framework, to your project and import the header in your .m file:
#import <AddressBook/AddressBook.h>
The real question, though, was how to set the name for the MKMapItem so it doesn't show up as "Unknown Location" in the maps app. That's as simple as setting the name property, probably just grabbing the title from your annotation:
mapitem.name = annotation.title;

Related

How to pass annotation title to another view controller

I am new to iOS programming, and I am having trouble with passing data between view controllers.
I have a view controller that geocodes an annotation on a map view, and sets the address as the title of the annotation view. The annotation view has a callout button that pushes another view to the stack, which has a label, and I want the address that was geocoded to show up as the label. Here is some code:
This is where I drop the pin:
-(void)press:(UILongPressGestureRecognizer *)recognizer
{
CGPoint touchPoint = [recognizer locationInView:worldView];
CLLocationCoordinate2D touchMapCoordinate = [worldView convertPoint:touchPoint toCoordinateFromView:worldView];
geocoder = [[CLGeocoder alloc]init];
CLLocation *location = [[CLLocation alloc]initWithCoordinate:touchMapCoordinate
altitude:CLLocationDistanceMax
horizontalAccuracy:kCLLocationAccuracyBest
verticalAccuracy:kCLLocationAccuracyBest
timestamp:[NSDate date]];
[geocoder reverseGeocodeLocation:location
completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"reverseGeocoder:completionHandler: called");
if (error) {
NSLog(#"Geocoder failed with error: %#", error);
}
if (placemarks && placemarks.count > 0)
{
CLPlacemark *place = [placemarks objectAtIndex:0];
_address = [NSString stringWithFormat:#"%# %#, %# %#", [place subThoroughfare], [place thoroughfare], [place locality], [place administrativeArea]];
if (UIGestureRecognizerStateBegan == [recognizer state]) {
_addressPin = [[MapPoint alloc]initWithCoordinate:touchMapCoordinate
title:_address];
[worldView addAnnotation:_addressPin];
}
}
}];
}
Here is the code for the view where I want the address to show up:
#import <UIKit/UIKit.h>
#class MapPoint;
#interface PinViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate,UITextFieldDelegate>
{
__weak IBOutlet UILabel *addressLabel;
}
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
#property (strong, nonatomic) MapPoint *pin;
-(void)takePicture:(id)sender;
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
#end
#import "PinViewController.h"
#import "MapPoint.h"
#interface PinViewController ()
#end
#implementation PinViewController
#synthesize imageView, pin;
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[addressLabel setText:[pin title]];
}
The class MapPoint has a title property and subtitle property. When the PinViewController is pushed to the top of the stack, I try to set the text of the label to the title of the pin, but the label does not show any text. Can someone help me out and tell me what I am missing? Your help is greatly appreciated.
You must share more code blocks for this question :) . instead of you have to get more idea about Communication in Object-Oriented Programs
You can read a good tutorial about Passing Data Between View Classes here
Read best way to pass an object between two views here
There is a good document that i found in Developer.apple regarding CommunicatingWithObjects
Also you can watch a good video here
Just create property for the label and pass the label text from callout tapped method
- (void)mapView:(MKMapView *)mapView annotationView:(MKPinAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
Details *det=[[Details alloc]init];
det.label.text=annotation.title;
[self.navigationController pushViewController:det animated:YES];
[det release];
}

MKAnnotation viewForAnnotation never called

I have already add UIViewController<MKMapViewDelegate> in .h and already add
-(void) viewDidLoad {
self.mapView.delegate = self;
}
but the method viewForAnnotation never called
MapViewController.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface Tela1ViewController : UIViewController<MKMapViewDelegate> {
IBOutlet MKMapView *mapView;
}
#property (nonatomic, retain) MKMapView *mapView;
#end
MapViewController.m
-(void) viewDidLoad {
self.mapView.delegate = self;
}
- (void)viewDidAppear:(BOOL)animated
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication shared
mapView = [[MKMapView alloc] initWithFrame:self.view.bounds];
mapView.mapType = MKMapTypeSatellite;
CLLocationCoordinate2D coord = {latitude: appDelegate.latitude, longitude: appDelegate.longitude};
MKCoordinateSpan span = {latitudeDelta:0.2, longitudeDelta: 0.2};
MKCoordinateRegion region = {coord, span};
[mapView setRegion:region];
PlaceMark *addAnnotation = [[PlaceMark alloc] initWithCoordinate:coord];
[mapView addAnnotation:addAnnotation];
[self.view addSubview:mapView];
}
- (MKAnnotationView *)mapView:(MKMapView *)map viewForAnnotation:(id <MKAnnotation>)annotation
{
NSLog(#"test");
}
Okay, couple of things to fix in your code ...
Delegate of MKMapView
Is nil. Why? Because you set delegate of MKMapView in viewDidLoad method. But when you look at viewDidAppear:, you're allocating new MKMapView and you're not setting delegate there. And because viewDidLoad is called before viewDidAppear:, delegate is simply nil = not set.
Calling super
When you override some methods, read documentation. Because you can find this in viewDidAppear: documentation for example:
You can override this method to perform additional tasks associated with presenting the view. If you override this method, you must call super at some point in your implementation.
MKMapView & IBOutlet
And also don't understand why do you have MKMapView as IBOutlet and then you're allocating new MKMapView and adding it as subview. Also if your IBOutlet is really connected to a MKMapView in your XIB, you'll end up with two MKMapViews, because the old one (from XIB) is not removed from superview.
You're clearly messing things up. Go and read more about UIKit, ...
Try replacing the last several lines of you viewDidAppear method with the following:
CLLocationCoordinate2D coord = {.latitude = location.latitude, .longitude = location.longitude};
MKCoordinateSpan span = {.latitudeDelta = 0.2, .longitudeDelta = 0.2};
MKCoordinateRegion region = {coord, span};
[mapView setRegion:region];
MapAnnotation *addAnnotation = [[MapAnnotation alloc] initWithCoordinate:coord];
[mapView addAnnotation:addAnnotation];
[self.view addSubview:mapView];

Adding pins to MapView

I was wondering how I would add pins to a MapView within an iPhone app. I want to have pins pinned in places that have the word "Tea" in their name and it would be impractical to place each pin in every place that contains that word, so I was wondering if there's some way to make it so that when the MapView is loaded, the pins are pinned into those places. I assume that this would be done with Google's Map API however I'm unsure as to how I'd exactly do this - does anyone know of any tutorials that would show to implement this.
So far, I have a simple view that contains a MapView as well as a corresponding view controller.
Thanks in advance!
You'll have to add instances of MKAnnotation to your MKMapView.
[mapView addAnnotation:annotation];
annotation is an instance of a class conforming to the MKAnnotation protocol. Read the corresponding documentation here:
http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKMapView_Class/MKMapView/MKMapView.html
http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKAnnotation_Protocol/Reference/Reference.html#//apple_ref/occ/intf/MKAnnotation
Sample code:
#interface MyAnnotation: NSObject <MKAnnotation>
{
CLLocationCoordinate2D coordinate;
NSString *title;
}
#end
#implementation MyAnnotation
#synthesize coordinate, title;
- (id) init
{
if ((self = [super init]))
{
coordinate.latitude = 0.0;
coordinate.longitude = 0.0;
title = NSLocalizedString(#"Tea");
}
return self;
}
#end
In your view controller:
- (void) viewDidLoad
{
[super viewDidLoad];
// custom initialiation; create map view
[self addPin]; // or with parameters, called multiple times, to add several annotations
}
- (void) addPin
{
MyAnnotation *ann = [[MyAnnotation alloc] init];
[mapView addAnnotation:ann];
[ann release];
}
Hope this helps.

Getting xml data in DetailView from a map view (Iphone IOS)

We are trying to create a mapview with annotations loaded from a xml file. This works so far, and is making use of the KMLViewer code on the apple developer library. Now we are trying to load the data from the XML file into the detailview but only the corresponding entry. So when you click on details on a city for instance, details must be loaded from the xml file of that city.
We are trying for days now but just don't know where to start. We have the following code now:
detailviewcontroller.m
#import "DetailViewController.h"
#implementation DetailViewController
#synthesize address;
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib
- (void)viewDidLoad
{
TabbedCalculationAppDelegate *appDelegate = (TabbedCalculationAppDelegate *)[[UIApplication sharedApplication] delegate];
address.text = appDelegate.addressInput1 ;
[super viewDidLoad];
}
- (void)viewDidUnload
{
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc
{
[super dealloc];
}
Map view
#import "locator.h"
#import "DetailViewController.h"
#implementation locator
#synthesize map, detailViewController, rightButton, customPinView;
- (void)viewDidLoad
{
[super viewDidLoad];
// create a custom navigation bar button and set it to always says "Back"
UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];
temporaryBarButtonItem.title = #"Back";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;
[temporaryBarButtonItem release];
// Locate the path to the route.kml file in the application's bundle
// and parse it with the KMLParser.
NSString *path = [[NSBundle mainBundle] pathForResource:#"branches" ofType:#"kml"];
kml = [[KMLParser parseKMLAtPath:path] retain];
// Add all of the MKOverlay objects parsed from the KML file to the map.
NSArray *overlays = [kml overlays];
[map addOverlays:overlays];
// Add all of the MKAnnotation objects parsed from the KML file to the map.
NSArray *annotations = [kml points];
[map addAnnotations:annotations];
// Walk the list of overlays and annotations and create a MKMapRect that
// bounds all of them and store it into flyTo.
MKMapRect flyTo = MKMapRectNull;
for (id <MKOverlay> overlay in overlays) {
if (MKMapRectIsNull(flyTo)) {
flyTo = [overlay boundingMapRect];
} else {
flyTo = MKMapRectUnion(flyTo, [overlay boundingMapRect]);
}
}
for (id <MKAnnotation> annotation in annotations) {
MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0, 0);
if (MKMapRectIsNull(flyTo)) {
flyTo = pointRect;
} else {
flyTo = MKMapRectUnion(flyTo, pointRect);
}
}
// Position the map so that all overlays and annotations are visible on screen.
MKCoordinateRegion mapRegion;
mapRegion.center.latitude = 51.522416;
mapRegion.center.longitude = 5.141602;
mapRegion.span.latitudeDelta = 5;
mapRegion.span.longitudeDelta = 5;
[map setRegion:mapRegion animated:YES];
}
#pragma mark MKMapViewDelegate
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
return [kml viewForOverlay:overlay];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
// if it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
// handle custom annotations
// // try to dequeue an existing pin view first
static NSString* BridgeAnnotationIdentifier = #"bridgeAnnotationIdentifier";
MKPinAnnotationView* pinView = (MKPinAnnotationView *)
[map dequeueReusableAnnotationViewWithIdentifier:BridgeAnnotationIdentifier];
if (!pinView)
{
// if an existing pin view was not available, create one
customPinView = [[[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:BridgeAnnotationIdentifier] autorelease];
customPinView.pinColor = MKPinAnnotationColorPurple;
customPinView.animatesDrop = YES;
customPinView.canShowCallout = YES;
// add a detail disclosure button to the callout which will open a new view controller page
//
// note: you can assign a specific call out accessory view, or as MKMapViewDelegate you can implement:
// - (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control;
//
rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
customPinView.rightCalloutAccessoryView = rightButton;
return customPinView;
}else{
return pinView;}
return nil;
}
#pragma mark -
#pragma mark MKMapViewDelegate
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
{
if (view.annotation == mapView.userLocation)
return;
rightButton = (DetailViewController *)view.annotation;
//show detail view using buttonDetail...
}
// the detail view does not want a toolbar so hide it
[self.navigationController setToolbarHidden:YES animated:YES];
[self.navigationController pushViewController:self.detailViewController animated:YES];
}
- (void)viewDidUnload
{
self.detailViewController = nil;
}
- (void)dealloc
{
[detailViewController release];
[super dealloc];
}
#end
As you can see the code is starting to look messy after trying tons of stuff, but we don't really know where to start.
Any help would be extremely appreciated
Thnx in advance!
Please have a look at the interface of the KMLPlacemark in KMLParser, there you can see what is exactly parsed and stored of an xml placemark element. For example the address is missing. So you will have to add all the information you want the parser to gather by implementing the fields in the KMLPlacemark class and alter the KMLParser methods:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName ...
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName ...
as well as some parts of the KMLPlacemark implementation. To fill the new fields with the parser you'll have to write methods like the - (void)beginName and - (void)endName. It will get a bit tricky when the elements you want to parse have children.
It could be helpful to split the KMLParser file into several files which contain one class each.
If you have achieved that and your placemark contains all the needed details you can catch the tap on an annotation with the MKMapViewDelegate protocol. Implement didDeselectAnnotationView, which could look like this:
- (void) mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view
{
// implementation example of placemarkForAnnotation below
KMLPlacemark * placemark = [kml placemarkForAnnotation:view.annotation];
MyDetailViewController * myDetailViewController = [[MyDetailViewController alloc] initWithPlacemark:placemark];
[self presentModalViewController:myDetailViewController animated:YES];
[myDetailViewController release];
}
In KMLParser add
- (KMLPlacemark *)placemarkForAnnotation:(id <MKAnnotation>)point
{
// Find the KMLPlacemark object that owns this point and return it
for (KMLPlacemark *placemark in _placemarks) {
if ([placemark point] == point)
return placemark;
}
return nil;
}
Hope I could point you in the right direction. It'll be some work ;)

Display annotation in Map kit

I am working with Mapkit and I have to show annotations in the map but I'm not able to display the annotation. Here's my code:
#interface MyMapView : UIViewController <MKAnnotation,MKMapViewDelegate>{
MKMapView *Obj_Map_View;
MKPlacemark *pmark;
MKReverseGeocoder *geocoder1;
}
#end
#import "MyMapView.h"
#implementation MyMapView
- (id)init {
if (self = [super init]) {
}
return self;
}
- (void)loadView {
[super loadView];
Obj_Map_View = [[MKMapView alloc]initWithFrame:self.view.bounds];
Obj_Map_View.showsUserLocation =YES;
Obj_Map_View.mapType=MKMapTypeStandard;
[self.view addSubview:Obj_Map_View];
Obj_Map_View.delegate = self;
CLLocationCoordinate2D cord = {latitude: 19.120000, longitude: 73.020000};
MKCoordinateSpan span = {latitudeDelta:0.3, longitudeDelta:0.3};
MKCoordinateRegion reg= {cord,span};
[Obj_Map_View setRegion:reg animated:YES];
//[Obj_Map_View release];
}
- (NSString *)subtitle{
return #"Sub Title";
}
- (NSString *)title{
return #"Title";
}
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation
{
MKPinAnnotationView *annov = [[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:#"Current location"];
annov.animatesDrop = TRUE;
[annotation title]==#"Current location";
annov.canShowCallout = YES;
[annov setPinColor:MKPinAnnotationColorGreen];
return annov;
}
The above code works fine and displays a map but not with annotation.
Typically, the class that conforms to the MKAnnotation protocol isn't the view controller, it's a data class.
You'll need to create another class, which I'll call "MyLandmarks" for the example.
#interface MyLandmarks : NSObject <MKAnnotation>
// Normally, there'd be some variables that contain the name and location.
// And maybe some means to populate them from a URL or a database.
// This example hard codes everything.
#end
#implementation MyLandmarks
-(NSString*)title {
return #"'ere I am, J.H.";
}
-(NSString*)subtitle {
return #"The ghost in the machine.";
}
-(CLLocationCoordinate2D) coordinate {
CLLocationCoordinate2D coord = {latitude: 19.120000, longitude: 73.020000};
return coord;
}
#end
Then, somewhere appropriate in your MyMapView class add:
MyLandmark *landmark = [[[MyLandmark alloc]init]autorelease];
[Obj_Map_View addAnnotation:landmark];
A couple other bits that other Objective-C developers working with you will appreciate:
To avoid confusion, don't call the class MyMapView if it descends from a UIViewController. Call it MyMapViewController, instead.
Classes start with a capital letter, variables start lowercase. Both are CamelCased. Obj_Map_View should be objMapView.
To add annotation use : addAnnotation:
read about it here