I am a beginner with XCode and I am trying to find a way for the User to input information inside a callbox of a pin they had placed on the map.
So if the User drops a pin and they tap on the pin, I want them to be able to type in a title and a subtitle of that location.
So far, I have created a map using MKMapView and I have implemented UILongPressGestureRecognizer and a void method that fires the moment a pin is created.
I am not sure what to do next? Thank you for your help.
Edit1: I don't have the required reputation to post an image, so here is a link of what I am trying to accomplish: http://imgur.com/gixFxpI
If it is possible to type in the title and subtitle without the use of a disclosure button, that would be swell. Later on I will develop the disclosure button.
Edit2: I have three new questions: 1. Why isn't my disclosure button appearing in my callout? 2. What is reuseidentifier for? Here is what I have so far.
I have embedded a navigation controller to my initial view controller that has the MKMapView in it. I created a second View Controller and I connected the MKMapView to the second View Controller and made my identifier for the second View Controller as 'heylisten' (good old Zelda).
My third question is in the prepareForSegue method, what information needs to go after the dot? destinationViewController.???? = sender.annotation;
// This is your callout box
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"overhere"];
annotationView.canShowCallout = YES;
UIButton *rightDisclosureButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView = rightDisclosureButton;
return annotationView;
}
// This is a segue when you tap the right disclosure button
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
[self performSegueWithIdentifier:#"heylisten" sender:view];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(MKAnnotationView *)sender
{
if ([segue.identifier isEqualToString:#"heylisten"])
{
ViewController *destinationViewController = segue.destinationViewController;
// grab the annotation from the sender
destinationViewController = sender.annotation;
}
}
If you're targeting iOS 8, use a UIAlertController:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIAlertController_class/
If you're targeting iOS 7 or earlier, use a UIAlertView:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIAlertView_Class/index.html#//apple_ref/occ/cl/UIAlertView
Both of these types have mechanisms to add an input text field and specify a callback to be executed with the text the user typed.
Related
I have a UITapGestureRecognizer that will hide and show a toolbar over my MKMap when the user taps the Map - simple.
However, when the user taps on an MKMapAnnotation, I do not want the map to respond to a tap in the normal way (above). Additionally, when the user taps elsewhere on the map to de-select an MKAnnotation callout, I also don't want the toolbar to respond. So, the toolbar should only respond when there are no MKAnnotations currently in selected state. Nor should it respond when the user clicks on an annotation directly.
So far, I have being trying the following action that reacts to the tap gesture on the map - however the Annotation View is never detected (the first if statement) and also, the annotation view is also launched regardless of this method.
-(void)mapViewTapped:(UITapGestureRecognizer *)tgr
{
CGPoint p = [tgr locationInView:self.mapView];
UIView *v = [self.mapView hitTest:p withEvent:nil];
id<MKAnnotation> ann = nil;
if ([v isKindOfClass:[MKAnnotationView class]])<---- THIS CONDITION IS NEVER MET BUT ANNOTATIONS ARE SELECTED ANYWAY
{
//annotation view was tapped, select it…
ann = ((AircraftAnnotationView *)v).annotation;
[self.mapView selectAnnotation:ann animated:YES];
}
else
{
//annotation view was not tapped, deselect if some ann is selected...
if (self.mapView.selectedAnnotations.count != 0)
{
ann = [self.mapView.selectedAnnotations objectAtIndex:0];
[self.mapView deselectAnnotation:ann animated:YES];
}
// If no annotation view is selected currently then assume control of
// the navigation bar.
else{
[self showToolBar:self.navigationController.toolbar.hidden];
}
}
}
I need to control the launch of the annotation call out programmatically and detect when the tap event has hit an annotation in order to achieve this.
Any help would be appreciated.
I think you will find the following links very useful:
http://blog.asynchrony.com/2010/09/building-custom-map-annotation-callouts-part-2/
How do I make a MKAnnotationView touch sensitive?
The first link discusses (among other things) how to prevent the propagation of touches to the annotations so that they selectively respond, and the second one how to detect the touches.
I think that because MKMapAnnotationView are on top of MKMapView, they will get the touch event and respond to it (be selected) so I don't think you need to select your annotation manually.
Then, if you have a look at Advanced Gesture Recognizer WWDC 2010 video, you will see that your MKMapView will receive tap event anyway, even if it's below the annotation view. That's probably why your -(void)mapViewTapped:(UITapGestureRecognizer *)tgr method get called.
Apart from that, I can't see why your if ([v isKindOfClass:[MKAnnotationView class]]) is never true. I do the exact same thing in my code and it works fine!
Finally, to answer your last question, if you don't want to do anything when the user is just trying to close the callout, you could keep track of a custom isCalloutOpen boolean value like this:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
//some code
_isCalloutOpen = YES;
}
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view {
// delay the reset because didDeselectAnnotationView could (and is often) called before your gesture recgnizer handler method get called.
[self performSelector:#selector(resetCalloutOpenState) withObject:Nil afterDelay:0.1];
}
- (void)resetCalloutOpenState {
_isCalloutOpen = NO;
}
- (void)mapViewTapped:(UITapGestureRecognizer *)tgr {
if (_isCalloutOpen) {
return;
}
}
Mapkit usage, here is my scenario: When the user chooses one of the pushpins on my map, then selects the callout associated with that pin, the user is to be be brought to another ViewController that will show information pertinent to their selection.
To set data for the destinationViewController, I am using
prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
This is working but I have an issue where I am not sure which annotation has been selected.
The following code illustrates my issue - the code works, but I have it hard coded to "objectAtIndex:0" instead of objectAtIndex:"whatever pin they selected"
[self.myImageViewController setAddressMessage: [[self.annotations objectAtIndex:0] houseAddress]];
In prepareForSegue, how do I know which pin they have selected?
Not knowing the rest of your code, I'm not sure if this will work but this is how I found the annotation in a similar situation where a button on the annotation view's callout was pressed.
-(void)buttonInCalloutWasTapped:(UIButton *)button {
if ([[[[button superview] superview] class] isSubclassOfClass:[MKAnnotationView class]]) {
MKAnnotation *annotation = [(MKPinAnnotationView *)[[button superview] superview] annotation];
[self.myImageViewController setAddressMessage: [annotation houseAddress]];
}
}
I got an app with MKMapView and large number of pins on this map.
Every pin got rightCalloutAccessoryView. I create it in this way:
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self action:#selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];
pinView.rightCalloutAccessoryView = rightButton;
How should i know, which pin was tapped? Thnx
In the showDetails: method, you can get the pin tapped from the map view's selectedAnnotations array. Even though the property is an NSArray, just get the first item in the array since the map view only allows one pin to be selected at a time:
//To be safe, may want to check that array has at least one item first.
id<MKAnnotation> ann = [[mapView selectedAnnotations] objectAtIndex:0];
// OR if you have custom annotation class with other properties...
// (in this case may also want to check class of object first)
YourAnnotationClass *ann = [[mapView selectedAnnotations] objectAtIndex:0];
NSLog(#"ann.title = %#", ann.title);
By the way, instead of doing addTarget and implementing a custom method, you can use the map view's calloutAccessoryControlTapped delegate method. The annotation tapped is available in the view parameter:
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control
{
NSLog(#"ann.title = %#", view.annotation.title);
}
Make sure you remove the addTarget from viewForAnnotation if you use calloutAccessoryControlTapped.
Titles and subtitles can be added to the user location that iOS shows using MKUserLocation. When the user taps on the location, these will show in a bubble above the location. The thought bubbles for other annotations can be shown by selecting the annotation with setSelected:animated: from MKAnnotationView. Unfortunately, MKUserLocation does not descend from MKAnnotationView.
How can I programmatically select the user location so the annotation appears over the user location without the user first tapping on it?
The documentation for MKAnnotationView says this about its setSelected:animated: method (and something similar for its selected property):
You should not call this method directly.
Instead, use the MKMapView method selectAnnotation:animated:. If you call it in the didAddAnnotationViews delegate method, you can be sure the annotation view is ready to show the callout otherwise calling selectAnnotation will do nothing.
For example:
-(void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
for (MKAnnotationView *av in views)
{
if ([av.annotation isKindOfClass:[MKUserLocation class]])
{
[mapView selectAnnotation:av.annotation animated:NO];
//Setting animated to YES for the user location
//gives strange results so setting it to NO.
return;
}
}
}
Ok, so I have a map view that has a bunch of annotations on it. Certain annotations when selected need to display extended info in a small table view which i am doing by resizing the mapview to half screen and animating into view a table in the bottom half. If another annotation is selected that doesn't need the extra info then in the didDeselectAnnotationView: method i hide the table and go back to the full map view, rinse and repeat.. So far so good, everything is working great.
The issue i am having though is that if a user selects another annotation while they currently have an annotation selected then didSelectAnnotationView delegate method gets called BEFORE the didDeselectAnnotationView.
This is obviously a problem because i am using these two methods to decide whether or not i need to display/hide the info table below the mapview, see code below:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
if ([view.annotation isKindOfClass:[MapLocation class]])
{
if ([self.selectedAnnotation numberOfEvents] == 1)
{
mapTableViewIsVisible = NO;
}
else if ([self.selectedAnnotation numberOfEvents] > 1)
{
// launch mini tableview
mapTableViewIsVisible = YES;
}
[self loadMapTableViewWithEvents:self.selectedAnnotation.events
forAnnotation:self.selectedAnnotation];
}
}
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view
{
if ([view.annotation isKindOfClass:[MapLocation class]])
{
mapTableViewIsVisible = NO;
[self loadMapTableViewWithEvents:nil forAnnotation:(MapLocation*)view.annotation];
}
}
So for example if i select an annotation that needs the maptable and i currently have a regular annotation selected then the mapTable is loaded when the didSelectAnnotationView method above is called, however it is immediately hidden again because the didDeselectAnnotationView is called right after.
So far i havent been able to figure out a way to fix this.
Any ideas??
You could check for the case where no annotations are visible in didDeselectAnnotationView and then clean up your tableview on this case only. As all other cases will be handled by didSelectAnnotation view.
Something like:
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view{
if([[mapView selectedAnnotations] count]==0){
mapTableViewIsVisible = NO;
[self loadMapTableViewWithEvents:nil forAnnotation:(MapLocation*)view.annotation];
}
}