I am trying to add annotations into my MKMapView and I want to set annotations to be clickable.(clickable feature I did in didAddAnnotationViews.)
Actually, this feature is working fine for IOS 6, but not working for IOS 7. I added a simple NSLog print statement in my didAddAnnotationViews method, and I found didAddAnnotationViews not response to addAnnotaion properly. I thought didAddAnnotationViews will be called each time in response to addAnnotaion . When I added several annotations only one log statement print, which means didAddAnnotationViews only execute once I think.
I'm using MapKit, MKMapView and this issue only occur in IOS 7. Can anyone give me a idea how to fix or find the reason why didAddAnnotationViews not working properly?
- (void)mapView:(MKMapView *)mv didAddAnnotationViews:(NSArray *)views
{
MKAnnotationView *annotationView = [views objectAtIndex:0];
MapViewAnnotation *annotation=annotationView.annotation;
NSLog(#"<==========didAddAnnotationViews=============> %#",annotation.title);
annotationView.canShowCallout=YES;
if(annotation.subtitle==nil){
annotationView.rightCalloutAccessoryView = nil;
}else{
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(annotation.coordinate, 11000, 11000);
[mv setRegion:region animated:YES];
}
}
The method signature implies that multiple annotation views are given at the same time:
- (void)mapView:(MKMapView *)mv didAddAnnotationViews:(NSArray *)views;
Try logging the array of views with:
NSLog(#"%#", views);
If you are lucky, you'll see all your annotations. Iterate over them instead of only looking at the first object at index 0.
Related
i wondering if someone may be able to help me. I am new to xcode and im trying to build a basic app. i have followed this tutorial
http://rshankar.com/how-to-add-annotation-to-mapview-in-ios/
i have copied the source code directly, i don't get any errors or warnings, however when running the app the pin locations do not show. Im not sure if they are there ( just invisible ) or whether they are not showing at all. I can't seem to find the issue.
Would anyone be able to suggest what the problem could be?
Thanks in advance.
actually managed to fix this, turns out the coordinates were round the wrong way!!!! Noob error!! I would like to change the callout on the pins if anyone could shed some light on where to change the code on the tutorial.
Thanks
To change the callout of the pins:
make the class that has your map view implement the <MKMapViewDelegate> protocol
set the delegate of your map view to that class (e.g. mapView.delegate = self;)
implement MKMapViewDelegate's (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation method in that class
something like this:
- (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
if ([annotation isKindOfClass:[NAME_OF_CLASS_IMPLEMENTING_MKANNOTATION_PROTOCOL class]])
{
// Try to dequeue an existing annotation view first
MKAnnotationView *annotationView = (MKAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:#"AnnotationViewIdentifier"];
if (!annotationView)
{
// If an existing pin view was not available, create one
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"AnnotationViewIdentifier"];
annotationView.canShowCallout = YES;
// set callout
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView = rightButton;
}
else
{
annotationView.annotation = annotation;
}
return annotationView;
}
return nil;
}
On your map pins (which should be made from a class that implements the MKAnnotation protocol), you should be able to set the title and subtitle properties.
Start a new page based application project in Xcode
Run the project and turn some pages
Rotate the simulator or device
=> The page view conroller switches back to the first page (january)
How can I prevent step 4. ?
EDIT:
This happens only the first time you rotate after the app started in simulator/device.
I use most recent Xcode 4.5 with iOS 6.0 Simulator and iOS 6 on my testing device.
The same thing happens when I download some other sample code from blogs / etc. Maybe an iOS 6 bug?
EDIT2:
I found out that the first page view that is passed to the UIPageViewController is not dealloced until first rotation. This really looks like a bug to me.
(UPDATE FROM 2014: This seems to have been fixed in iOS7, if you start again from a new Page View application template.)
I've experienced this bug as well. It seems to kick in any time after the main view reappears. My app has several full-screen modals in it, and after those go away the same behaviour occurs.
This happens in XCode 4.5.1 and iOS6 - I 'fixed' this by re-downloading XCode 4.4 and reverting my app back to iOS5.1. Obviously not a great long-term solution. I filed this in Radar and got a note back that it was already logged.
FWIW I noticed that iBooks had this same bug in it right after iOS6 came out, but they seem to have fixed it in a recent update.
Here's how I managed to fix this problem in my app. I'm afraid it's kind of a hacky solution, but it's a quirky bug.
Context: My app is a diary (it's called Remembary) and each page is a different day's diary entry. I have a singleton class called "AppContext" that keeps track of various app-level values, such as the currently showing diary entry object, the current date, and the like. Each day's dataViewController also keeps track of its own diary entry.
The trickiest part was finding a context where I could catch that the app was showing the wrong page. It turns out that this is in [RootViewController viewDidLayoutSubviews], so I added the following to that method:
// get the currently displaying page
DataViewController *currentPage = self.pageViewController.viewControllers[0];
// check if we're showing the wrong page
if ([currentPage myEntry] != [AppContext getCurrentEntry]) {
// jump to the proper page (the delay is needed to ensure that the rotation has fully completed)
[self performSelector:#selector(forceJumpToDate:)
withObject:[AppContext getCurrentEntryDate]
afterDelay:0.5];
}
Here's the forceJumpToDate function, which basically gets a new page based on the current date and tells the pageViewController to jump to it without animating:
- (void) forceJumpToDate:(NSDate *)targetDate {
DataViewController *targetPage = [self.modelController viewControllerForDate:targetDate
storyboard:self.storyboard];
NSArray *viewControllers = [NSArray arrayWithObject:targetPage];
[self.pageViewController setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:NULL];
}
The user might notice a brief hiccup on the screen as the new page is forced into place, but this only happens if they would otherwise be getting the wrong page, so it's still an improvement.
This was seriously interfering with my ability to upgrade my app to iOS6, so I'm glad I finally figured it out.
Here is my solution:
// RootViewController.m
#import "RootViewController.h"
#import "ModelController.h"
#import "DataViewController.h"
#interface RootViewController ()
#property (readonly, strong, nonatomic) ModelController *modelController;
//added
#property (strong, nonatomic) DataViewController *currentViewController;
#end
#implementation RootViewController
#synthesize modelController = _modelController;
//added
#synthesize currentViewController = _currentViewController;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Configure the page view controller and add it as a child view controller.
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.delegate = self;
DataViewController *startingViewController = [self.modelController viewControllerAtIndex:0 storyboard:self.storyboard];
NSArray *viewControllers = #[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];
self.pageViewController.dataSource = self.modelController;
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
// Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages.
CGRect pageViewRect = self.view.bounds;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
pageViewRect = CGRectInset(pageViewRect, 40.0, 40.0);
}
self.pageViewController.view.frame = pageViewRect;
[self.pageViewController didMoveToParentViewController:self];
// Add the page view controller's gesture recognizers to the book view controller's view so that the gestures are started more easily.
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
//added
self.currentViewController = self.pageViewController.viewControllers[0];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (ModelController *)modelController
{
// Return the model controller object, creating it if necessary.
// In more complex implementations, the model controller may be passed to the view controller.
if (!_modelController) {
_modelController = [[ModelController alloc] init];
}
return _modelController;
}
#pragma mark - UIPageViewController delegate methods
/*
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
}
*/
//added
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
self.currentViewController = self.pageViewController.viewControllers[0];
}
- (DataViewController *)currentViewController
{
if (!_currentViewController) _currentViewController = [[DataViewController alloc] init];
return _currentViewController;
}
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
if (UIInterfaceOrientationIsPortrait(orientation) || ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)) {
// In portrait orientation or on iPhone: Set the spine position to "min" and the page view controller's view controllers array to contain just one view controller. Setting the spine position to 'UIPageViewControllerSpineLocationMid' in landscape orientation sets the doubleSided property to YES, so set it to NO here.
//deleted: UIViewController *currentViewController = self.pageViewController.viewControllers[0];
//changed to self.currentViewController
NSArray *viewControllers = #[self.currentViewController];
[self.pageViewController setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:NULL];
self.pageViewController.doubleSided = NO;
return UIPageViewControllerSpineLocationMin;
}
// In landscape orientation: Set set the spine location to "mid" and the page view controller's view controllers array to contain two view controllers. If the current page is even, set it to contain the current and next view controllers; if it is odd, set the array to contain the previous and current view controllers.
// deleted: DataViewController *currentViewController = self.pageViewController.viewControllers[0];
//deleted: NSArray *viewControllers = nil;
//added
NSArray *viewControllers = #[self.currentViewController];
//changed currentViewController to self.currentViewController
NSUInteger indexOfCurrentViewController = [self.modelController indexOfViewController:self.currentViewController];
if (indexOfCurrentViewController == 0 || indexOfCurrentViewController % 2 == 0) {
UIViewController *nextViewController = [self.modelController pageViewController:self.pageViewController viewControllerAfterViewController:self.currentViewController];
viewControllers = #[self.currentViewController, nextViewController];
} else {
UIViewController *previousViewController = [self.modelController pageViewController:self.pageViewController viewControllerBeforeViewController:self.currentViewController];
viewControllers = #[previousViewController, self.currentViewController];
}
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
return UIPageViewControllerSpineLocationMid;
}
#end
What is it you want to prevent? Do you want to prevent rotation? If that is what you want, modify the shouldAutorotateToInterfaceOrientation return value in the RootViewController.m implementation file.
When I did this, the App was able to keep the same page (month) even after rotating the device. I used the simulator and tried on both iPhone and iPad. On the iPad, in landscape mode, it showed two months at a time, but then when rotated back to portrait, still kept the first of the two months that was displayed. This was when I incremented to June. I used the default project without changing a line of code.
Today I found out that in my app I could just use the following to remove the bug (but I have no clue why).
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
...
self.pageViewController.view.hidden = YES;
}
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
self.pageViewController.view.hidden = NO;
}
I have a single annotation on a map view. I can select it programmaticly, but the I tap it nothing happens. Could you help me? Did anyone encounter similar problem? Here is mehod for setting up anotations:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
MKAnnotationView *aView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"MapVC"];
if (!aView) {
aView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"MapVC"];
aView.canShowCallout = YES;
aView.draggable=YES;
aView.leftCalloutAccessoryView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
// could put a rightCalloutAccessoryView here
}
aView.annotation = annotation;
[(UIImageView *)aView.leftCalloutAccessoryView setImage:nil];
return aView;
}
And adding them to map view:
- (void)updateMapView
{
if (self.mapView.annotations) [self.mapView removeAnnotations:self.mapView.annotations];
if (self.annotation) [self.mapView addAnnotation:self.annotation];
}
And mehod reacting to pressing of annotations:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)aView
{
NSLog(#"did select annotation");
}
By the way, method [self.mapView selectAnnotation:annotation] works, but doesn't put up a callout(i checked it with breakpoint). While just taping annotation doesn't(again cheked through breakpoints).
If an annotation's title is nil or blank, the callout will not show (even if everything else is set properly including canShowCallout).
When you tap on an annotation, the didSelectAnnotationView delegate method will get called and if the annotation has a non-blank title, the callout will be displayed.
Regarding your question in the comments:
...is it right I have a seperate class to wrap all my data in to, my
annotation class contains an instance of that data class?
There's nothing wrong with this.
If you want to keep map-related logic separate from the base class, that's fine and probably a good idea for a complex app where the base data class may be used for more than just annotations.
If your app is very simple and the data is only used for annotations, you could keep things very simple and combine the two but it's not a requirement.
As long as you stick to using direct references instead of trying to, for example, use array indexes or view/button tags to link back to some data object from the annotation, the "right" class implementation depends on what works for your app.
Try setting canShowCallout property of the MKAnnotationView to YES in case you didn't.
I've been playing around with the MKMapView and trying get my head around how the MKMapViewDelegate system works. So far I have no luck in getting the didAddAnnotationViews to get called when the current location marker is added.
I have set my app delegate to implement MKMapViewDelegate, I have an Outlet to the MapView in my xib and have set the delegate property of the MapView to be self, as in the app delegate instance. I have implemented didAddAnnotationViews in the app delegate which I simply NSLog any calls to it as shown below. The map is set to show current location which it does and adds the blue pin annotation on startup, but for some reason didAddAnnotationViews is not being hit.
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views{
NSLog(#"Annotation added!");
}
Any ideas what I might have missed?
I came across the same issue in BNR. Here is what I ended up using:
// Tell MKMapView to zoom to current location when found
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
NSLog(#"didUpdateUserLocation just got called!");
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance([userLocation coordinate], 250, 250);
[mapView setRegion:region animated:YES];
}
mapView:didAddAnnotations: is only called in response to addAnnotation: or addAnnotations:. The users location pin will not trigger this delegate method.
Just wanted to confirm that I was able to get this working using
- (void)mapView:(MKMapView *)mv didAddAnnotationViews:(NSArray *)views
{
MKAnnotationView *annotationView = [views objectAtIndex:0];
id <MKAnnotation> mp = [annotationView annotation];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance([mp coordinate], 250,250);
[mv setRegion:region animated:YES];
}
Make sure you are using
mapView.delegate = self;
or
[mapView setDelegate:self];
I am trying to make a map, where I can see my current location, and see what the street is called.
so far, I am able to put a pin on my map, but for some reason, I am not getting the callout.
and I have put a NSLog in my viewForAnnotation method, but it is not being called, so i wasn't able to test it.
can someone help me?
-(void)lat:(float)lat lon:(float)lon
{
CLLocationCoordinate2D location;
location.latitude = lat;
location.longitude = lon;
NSLog(#"Latitude: %f, Longitude: %f",location.latitude, location.longitude);
//One location is obtained.. just zoom to that location
MKCoordinateRegion region;
region.center=location;
//Set Zoom level using Span
MKCoordinateSpan span;
span.latitudeDelta=.005f;
span.longitudeDelta=.005f;
region.span=span;
[map setRegion:region animated:TRUE];
//MKReverseGeocoder *geocoder=[[MKReverseGeocoder alloc] initWithCoordinate:location];
//geocoder.delegate=self;
//[geocoder start];
if (cPlacemark != nil) {
[map removeAnnotation:cPlacemark];
}
cPlacemark=[[CustomPlacemark alloc] initWithCoordinate:location];
cPlacemark.title = mPlacemark.thoroughfare;
cPlacemark.subtitle = mPlacemark.locality;
[map addAnnotation:cPlacemark];
[cPlacemark release];
[mLocationManager stopUpdatingLocation];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
// try to dequeue an existing pin view first
if ([annotation isKindOfClass:[CustomPlacemark class]]){
MKPinAnnotationView *pinView=(MKPinAnnotationView *)[map dequeueReusableAnnotationViewWithIdentifier:#"customIdentifier"];
if (!pinView)
{
// if an existing pin view was not available, create one
MKPinAnnotationView* cPinAnnoView = [[[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:#"customIdentifier"] autorelease];
cPinAnnoView.pinColor = MKPinAnnotationColorPurple;
cPinAnnoView.animatesDrop = YES;
cPinAnnoView.canShowCallout = YES;
// Add button
UIButton *leftButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[leftButton addTarget:self action:#selector(annotationViewClick:) forControlEvents:UIControlEventTouchUpInside];
cPinAnnoView.leftCalloutAccessoryView = leftButton;
} else
{
pinView.annotation = annotation;
}
return pinView;
}
return nil;
}
Right now I have customized my viewForAnnotation to be like this.
But I still can't get a callout from my pin and the pin remains red.
But it should be purple of nothing at all
I had the same problem which was not setting the MapView delegate to the File Owner.
Open your nib
Right click on the MapView
Drag the delegate to the File's Owner
I had the same problem, as you mentioned. The delegate had been set to ViewController, but the viewForAnnotation selector was not being called. After some checks, I realized if you do not call addAnotation in the main thread, mapView would not call viewForAnnotation, so following update resolved my problem:
Before:
[_mMapView addAnnotation:marker];
After:
dispatch_async(dispatch_get_main_queue(), ^{
[_mMapView addAnnotation:marker];
});
In order to get the viewForAnnotation to be called, add mapView.delegate=self; to e.g. the viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
mapView.delegate=self;
}
Could it be that your annotation has been added outside the current view area of the MKMapView?
For storyboard, Ctl drag the MKMapView to the orange circle on the bottom bar of ViewController, and select delegate.
This will solve the problem.
As vatrif mentioned in the comments, you must set your delegate BEFORE adding annotations to your MKMapView object.
Others have already explained, odds are high you have not connected your mapview delegate to your controller. Its the first thing to check
i have been working in ios 9 Mapview related app and I experienced the same problem.
somehow I solved my problem, in my case im resizing the mapview.
I added delegate after i resize the mapview. it works now perfectly.!
After having set the delegate for the mapview if still the viewforannotation not getting called then this is something which you have missed - set the self.mapView.showsUserLocation to YES, in interface builder you can tick the shows userLocation option in attributes inspector.