iOS - How to use mkmapview as location picker - iphone

In my app I have two address UITextfields to be filled up using address text. I want this behavior:
As soon as user enters address textfield, a map appears from down. (already done)
User plays with the map. As he does that, a floating annotation object appears which is just about to drop.
A button (a subview of mkmapview), when pressed, drops this floating annotation on to the map.
As soon as user presses Done button, the annotation location on the map is reverse-geocoded.
The map view disappears (already done).
The UITextfield is populated with address text corresponding to center of the map view.
Same is repeated for another UITextfield.
I believe this is a very straight use case of mkmapview. But can't find any examples of this already done.
Am I missing something pretty obvious in UIKit?
Many thanks for your help....
EDIT:
I read up Apple's docs, and I found many solutions that point towards this.
However all of them seems to assume that MKPinAnnotationView (or MKAnnotationView) is overridden.
Is it necessary?
If yes, how much I need to provide in the subclass apart from dragging?

No need to subclass MKPinAnnotationView. Just use it. You only should be subclass it if you're looking for some custom behavior. But it is useful to write a viewForAnnotation so you can configure it properly. But typically I find the configuration of a standard MKPinAnnotationView to be simple enough that no subclassing is needed:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
MKPinAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"DroppedPin"];
annotationView.draggable = YES;
annotationView.canShowCallout = YES;
annotationView.animatesDrop = YES;
return annotationView;
}
Having said that, it is not uncommon to have your own annotation class. I might do this for at least two reasons:
You might keep the reverse geocoded MKPlacemark as a property of your annotation. Logically, the geocoded information seems like a property of the annotation, not of the view. You can then inquire this placemark property of the dropped pin to get whatever information you need to pass back to your other view.
If you want, you could configure your annotation to both perform the reverse geocode lookup which would the placemark property, but also change the title to the reverse geocoded address when you change its coordinate, too. This way, the user is getting active feedback about what the reverse geocoding as they're dragging and dropping the pin on the map, but the code is still super simple:
Thus, you might have an annotation class like:
#interface DroppedAnnotation : NSObject <MKAnnotation>
#property (nonatomic, strong) MKPlacemark *placemark;
#property (nonatomic) CLLocationCoordinate2D coordinate;
#property (nonatomic, strong) NSString *title;
#property (nonatomic, strong) NSString *subtitle;
#end
#implementation DroppedAnnotation
- (void)setCoordinate:(CLLocationCoordinate2D)coordinate
{
CLLocation *location = [[CLLocation alloc] initWithLatitude:coordinate.latitude
longitude:coordinate.longitude];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
// do whatever you want here ... I'm just grabbing the first placemark
if ([placemarks count] > 0 && error == nil)
{
self.placemark = placemarks[0];
NSArray *formattedAddressLines = self.placemark.addressDictionary[#"FormattedAddressLines"];
self.title = [formattedAddressLines componentsJoinedByString:#", "];
}
}];
_coordinate = coordinate;
}
#end
And your view controller can use this new class:
#property (nonatomic, weak) id<MKAnnotation> droppedAnnotation;
- (void)dropPin
{
// if we've already dropped a pin, remove it
if (self.droppedAnnotation)
[self.mapView removeAnnotation:self.droppedAnnotation];
// create new dropped pin
DroppedAnnotation *annotation = [[DroppedAnnotation alloc] init];
annotation.coordinate = self.mapView.centerCoordinate;
annotation.title = #"Dropped pin"; // initialize the title
[self.mapView addAnnotation:annotation];
self.droppedAnnotation = annotation;
}
To be clear, you don't need your own annotation class. You could use the standard MKPointAnnotation for example. But then your view controller has to keep call and keep track of reverse geocoded information itself. I just think the code is a little cleaner and more logical when you use a custom annotation class.

Related

iphone: How to show different image for every pin point on MapKit?

I want to put Start and End image with overlay in an iPhone/iPad application. I have start and end Lattitude and Longitude values and want to draw overlay between start and end points and put start image on Start point and End Image on End point.
I have googled but What I found is MapKit gets one image and set it on both Start and End points, could not find any help for 2nd image.
like
annotationView.image=[UIImage imageNamed:#"parkingIcon.png"];
It only set one image for both start and end points. But I want to put different images for both points.
Please help.
I got that ... thanks for all who tried to help me out. the Complete solution is
Create a class
#interface MyAnnotationClass : NSObject <MKAnnotation> {
NSString *_name;
NSString *_description;
CLLocationCoordinate2D _coordinate;
}
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSString *description;
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
-(id) initWithCoordinate:(CLLocationCoordinate2D) coordinate;
ViewDidLoad method code :
mapView.delegate = self;
//Initialize annotation
MyAnnotationClass *commuterLotAnnotation=[[MyAnnotationClass alloc] initWithCoordinate:CLLocationCoordinate2DMake( 39.047752, -76.850388)];
commuterLotAnnotation.name = #"1";
MyAnnotationClass *overflowLotAnnotation=[[MyAnnotationClass alloc] initWithCoordinate:CLLocationCoordinate2DMake( 39.047958, -76.852520)];
overflowLotAnnotation.name = #"2";
//Add them to array
self.myAnnotations=[NSArray arrayWithObjects:commuterLotAnnotation, overflowLotAnnotation, nil];
//Release the annotations now that they've been added to the array
[commuterLotAnnotation release];
[overflowLotAnnotation release];
//add array of annotations to map
[mapView addAnnotations:_myAnnotations];
viewForAnnotation code :
-(MKAnnotationView *)mapView:(MKMapView *)MapView viewForAnnotation:(id<MKAnnotation>)annotation{
static NSString *parkingAnnotationIdentifier=#"ParkingAnnotationIdentifier";
if([annotation isKindOfClass:[MyAnnotationClass class]]){
//Try to get an unused annotation, similar to uitableviewcells
MKAnnotationView *annotationView=[MapView dequeueReusableAnnotationViewWithIdentifier:parkingAnnotationIdentifier];
//If one isn't available, create a new one
if(!annotationView){
annotationView=[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:parkingAnnotationIdentifier];
if([((MyAnnotationClass *)annotation).name isEqualToString: #"1"]){
annotationView.image=[UIImage imageNamed:#"passenger.png"];
}
else if([((MyAnnotationClass *)annotation).name isEqualToString: #"2"]){
annotationView.image=[UIImage imageNamed:#"place.png"];
}
}
return annotationView;
}
return nil;
}
This is how you can add separate image for every point on MapKit.
you have to use the -(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation; , a MKAnnotation delegate method
wich allow you to set a given annotation on the map.
This method is returned for every annotations, you just have to find the end and start annotations and set image to it
First, you have to implement the MKMapViewDelegate protocol and set the delegate class to your map.
In the MKMapView Delegate, declare the method:
- (MKAnnotationView *)mapView:(id)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if (annotation == [mapView userLocation]) // The user's blue point : no customisation.
return nil;
MKAnnotationView * annView = [mapView dequeueReusableAnnotationViewWithIdentifier:/* your specific identifier */];
if (annView == nil) {
annView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:/* your specific identifier */] autorelease];
}
annView.image = /* your image */;
return annView
}
But you have to detect if the annotation is the starting one or the ending one. You can set a specific title to easily retrieve it (like #"start") or subclass your MKAnnotations by adding a discriminating property.

How to show a call out for object that implements MkAnnotation protcol ?

I have an object that implements the MKAnnotation protocol:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface VoiceMemoryAnnotation : NSObject <MKAnnotation> {
NSString * blobkey;
}
#property (nonatomic, retain) NSString * blobkey;
-(id)initWithBlobkey:(NSString *) key andCoordinate:(CLLocationCoordinate2D) c;
#end
Adding this object a map works perfectly since I can see the red pins being dropped. However, the problem arises when I want to set this object to show a callout.
I cannot do annotation.showCallOut=YES because an "MkAnnotation" does not have this property, but a MkAnnotationView does. How do I get around this?
I tried to implement the map callback "viewForAnnotation" to check for "VoiceMemoryAnnotation" and I try to return a new "MkAnnotationView" and set it's callout = YES, but I start to get a segmentation fault when I do this.
Any ideas what I"m doing wrong?
First you need to create your annotation object (the one that implements the MKAnnotation protocol) and add it to your map using something like
VoiceMemoryAnnotation*VMA = [[VoiceMemoryAnnotation alloc] init];
VMA.title = #"Title String";
VMA.subtitle = #"Subtitle String";
[self.mapView addAnnotation:VMA];
That will automatically call the following method which you will need to implement:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKPinAnnotationView*singleAnnotationView = [[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:nil];
singleAnnotationView.canShowCallout = YES;
return singleAnnotationView;
}
In this implementation MKAnnotationView won't work, it needs to be MKPinAnnotationView.
I'm not sure I completely understand your question, but I wonder, is MKMapViews's - (void)selectAnnotation:(id <MKAnnotation>)annotation animated:(BOOL)animated what you're looking for?

Hide annotation subtitle, or change color

In my app am adding subtitle to annotation pin but don't want to show with pin. I am just using it for coding purpose. How can I hide or change subtitle color?
If I understand your question correctly, you are using pin subtitle for some processing and doesn't want to show it on pin. If you are using default callout then it is not possible to change colour of subtitle or to hid it once you assign annotation.subtitle = #"some subtitle".
I would suggest, add another variable to annotation and assign subtitle to that variable (annotation.newSubtitle = #"some subtitle")and don't assign anything for subtitle attribute. All the processing you want to do then can be done with annotation.newSubtitle.
If you are reluctant to add new variable to annotation then you will have to implement custom callout which is tedious.
EDIT:
You will find following useful to assign NSString to your annotation.
#interface MyAnnotation : NSObject<MKAnnotation> {
NSString *newSubTitle;
}
#property(retain,readwrite, nonatomic) NSString *newSubTitle ;
#end
In implementation part
#implementation MyAnnotation
#synthesize mSubTitle;
-(void)setNewSubTitle:(NSString *)SubTitle{
self.newSubTitle = SubTitle;
}
#end
And for assigning
[annotation setNewSubTitle: #"some text"];
for accessing
myString = annotation.newSubtitle
If you want access any string for every specific annotation, you can use NSMutableDictionary and add your annotation as key. and later you could access this in
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {

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.

Object-oriented design question, iPhone

Sorry I'm still a noob and just learning to program as I go and want to start out on the right foot by learning good design up front. I am using the CLLocationManager and MKReverseGecoder to get my location. In my MKReverseGecoderDelegate method, I create my annotation to show on the MKMapView. In my callout, I use a detail disclosure indicator to bring up another UITableView that displays your current address nicely as opposed to looking at the little black callout bubble.
What is a good way for my DetailViewController (the UITableView) to get the data? Do I have my first class have ivars for address, state, zipcode. In my MKReverseGecoderDelegate, set those ivars when I get that information. (The reason I would think I would need ivars is because my method to get that information in the MKReverseGeocoderDelegate is separate from the displayDetailViewController). And then do I have my DetailViewController have those same values, and when I go to display the DetailViewController, set those same variables? It seems redundant.
Any help would be greatly appreciated. Thanks!
One option
Declare custom class inheriting NSObject like
#interface YourClassName : NSObject
{
NSString *address;
NSString *state;
NSString *zipcode;
}
#property(nonatomic, retain) NSString *address;
#property(nonatomic, retain) NSString *state;
#property(nonatomic, retain) NSString *zipcode;
#end
#implementation YourClassName
#synthesize address,state,zipcode;
-(void)dealloc
{
[super dealloc];
[address release];
[state release];
[zipcode release];
}
#end
//Create object of YourClassName and set values
YourClassName *objYourClassName = [[YourClassName alloc] init];
objYourClassName.address = #"YourValue";
objYourClassName.state = #"YourValue";
objYourClassName.zipcode = #"YourValue";
Pass this object to your DetailViewController by one method after creating method like
-(void)setDetailsForDetailViewController:(YourClassName*)pObjYourClassName
{
//self.objOfYourClassName is property declared in your detailviewcontroller.
self.objOfYourClassName = pObjYourClassName; //You can use self.objOfYourClassName to set values in TableViewController.
}
If you stuck any where let me know I would be glad to help you fix that.
If you are doing the reverse geocoding on demand, initialize the DetailViewController with the coordinate of the annotation. Something like this:
- (id)initWithCoordinate:(CLLocation*)location {
if (self = [super initWithNibName:#"DetailController" bundle:nil]) {
self.location = location;
}
return self;
}
This is a common pattern to create the controllers, because it makes it clear for the controller's user that the controller depends on a location parameter. The other alternatives (global variables, or a singleton) are not so clean because they hide information and make the controller harder to understand and unit test.
Then let the controller launch an asynchronous task to do the geocoding, set itself as delegate, and present the information when it's done.