When a user selects a menu option I currently have the location name sent to my map view via "stringToDisplay" and a pin drops on the map as expected, however, it does not center the pin or zoom on the location on the first attempt. Every attempt after the first is flawless. How can I make the map zoom to my location(s) on the first attempt? Your input is greatly appreciated.
I have multiple IF statements contained in my viewWillAppear, but see below for one example of a location which experiences this problem:
-(void)viewWillAppear:(BOOL)animated
{
CLLocationCoordinate2D location;
NSLog(#"self.stringToDisplay = %#", self.stringToDisplay);
MKCoordinateRegion region;
MKCoordinateSpan span;
span.latitudeDelta=0.1;
span.longitudeDelta=0.1;
region.span=span;
region.center=location;
[mapView setRegion:region animated:TRUE];
[mapView regionThatFits:region];
if ([self.stringToDisplay isEqualToString: #"August First Bakery & Café"])
{
location.latitude = (double) 44.475486;
location.longitude = (double) -73.2172641;
MapViewAnnotation *newAnnotation = [[MapViewAnnotation alloc] initWithTitle:#"August First Bakery & Café" andCoordinate:location];
[self.mapView addAnnotation:newAnnotation];
[mapView setCenterCoordinate:location animated:YES];
}
}
Set a breakpoint in the viewWillAppear. Check to see if mapView is nil when you're doing the setRegion/regionThatFits. If it is you may need to move the code to viewDidAppear. - compliments of Phlibbo
Related
The problem is that removing annotation disables custom annotation button for rightCalloutAccessoryView.
When custom button is tapped, the protocol method mapView:annotationView:calloutAccessoryControlTapped is called but sender method for custom button is not. Firstly added annotation to map works fine, the problem starts with initialization of second annotation.
Here is the code:
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBarText {
[searchBarText resignFirstResponder];
[self.searchTable dismissViewControllerAnimated:YES completion:nil];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
//Geocoding search bar text (adress).
[geocoder geocodeAddressString:searchBarText.text completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
if (error) {
NSLog(#"Error with geocoding: %#", error);
} else {
//To remove previous annotation from MapView using its reference.
if (self.previousAnnotation != nil) {
[self.mapView removeAnnotation:self.previousAnnotation];
self.previousAnnotation = nil;
//To invoke result as the placemark object.
CLPlacemark *placemark = placemarks.firstObject;
//Adjusted region setup and data.
MKCoordinateRegion region = MKCoordinateRegionMake(placemark.location.coordinate, MKCoordinateSpanMake(0.01, 0.01));
MKCoordinateRegion adjustedRegion = [self.mapView regionThatFits:region];
[self.mapView setRegion:adjustedRegion animated:YES];
CustomAnnotation *customAnnotation = [[CustomAnnotation alloc] initWithTitle:placemark.name andLocation:CLLocationCoordinate2DMake(region.center.latitude, region.center.longitude)];
[self.mapView addAnnotation:customAnnotation];
//To save reference of newly added annotation.
self.previousAnnotation = customAnnotation;
} else if (self.previousAnnotation == nil) {
//To invoke result as the placemark object.
CLPlacemark *placemark = placemarks.firstObject;
//Adjusted region setup and data.
MKCoordinateRegion region = MKCoordinateRegionMake(placemark.location.coordinate, MKCoordinateSpanMake(0.01, 0.01));
MKCoordinateRegion adjustedRegion = [self.mapView regionThatFits:region];
[self.mapView setRegion:adjustedRegion animated:YES];
CustomAnnotation *customAnnotation = [[CustomAnnotation alloc] initWithTitle:placemark.name andLocation:CLLocationCoordinate2DMake(region.center.latitude, region.center.longitude)];
[self.mapView addAnnotation:customAnnotation];
//To save reference of newly added annotation.
self.previousAnnotation = customAnnotation;
}
}
}];
}
The logic seems fine. If I initialize annotations with the code below no problem occurs, I tap the button and button sender method is being called with every new annotation.
//To invoke result as the placemark object.
CLPlacemark *placemark = placemarks.firstObject;
//Adjusted region setup and data.
MKCoordinateRegion region = MKCoordinateRegionMake(placemark.location.coordinate, MKCoordinateSpanMake(0.01, 0.01));
MKCoordinateRegion adjustedRegion = [self.mapView regionThatFits:region];
[self.mapView setRegion:adjustedRegion animated:YES];
CustomAnnotation *customAnnotation = [[CustomAnnotation alloc] initWithTitle:placemark.name andLocation:CLLocationCoordinate2DMake(region.center.latitude, region.center.longitude)];
[self.mapView addAnnotation:customAnnotation];
Seems the problem exists between annotation removal and initialization of new annotation but I can't figure it out.
EDIT (more issue details)
From presenting second annotation on a map the callout button doesnt call it method (eventhough tap is detected). So for instance:
I search for adress1, I tap callout custom button, custom button method is called.
I search for adress2, adress1 callout bubble is correctly removed, new adress2 callout bubble appears on the map with correct coordinates, but custom button remains in selected state and tapping it doesnt call its method.
EDIT 2
Removal of annotations is correct (new one replaces old one, I checked it with logging self.mapView.annotations, also I checked placemark objects, with a new search the new location is the result).
Ok, I figured it out.
The problem was that after annotation removal, annotation view is queued internally for later reuse (according to MKMapView class reference). All you need to do is to use dequeueReusableAnnotationViewWithIdentifier: method after calling removeAnnotation:.
if (self.previousAnnotation != nil) {
[self.mapView removeAnnotation:self.previousAnnotation];
//Set it nil before attaching it with new reference.
self.previousAnnotation = nil;
//To invoke result as the placemark object.
CLPlacemark *placemark = placemarks.firstObject;
//Adjusted region setup and data.
MKCoordinateRegion region = MKCoordinateRegionMake(placemark.location.coordinate, MKCoordinateSpanMake(0.01, 0.01));
MKCoordinateRegion adjustedRegion = [self.mapView regionThatFits:region];
[self.mapView setRegion:adjustedRegion animated:YES];
CustomAnnotation *customAnnotation = [[CustomAnnotation alloc] initWithTitle:placemark.name andLocation:CLLocationCoordinate2DMake(region.center.latitude, region.center.longitude)];
[self.mapView dequeueReusableAnnotationViewWithIdentifier:#"CustomAnnotation"];
[self.mapView addAnnotation:customAnnotation];
//To save reference of newly added annotation.
self.previousAnnotation = customAnnotation;
i have a map and i want to set the user location ( blue dot ) when i open it.i tried lot of things and didn't work.please help me
MKCoordinateRegion region;
region.center.latitude=BG_LATITUDE;
region.center.longitude=BG_LONGITUDE;
region.span.latitudeDelta=SPAN_VALUE;
region.span.longitudeDelta=SPAN_VALUE;
[self.mapView setRegion:region animated:YES];
NSMutableArray *annotations =[[NSMutableArray alloc]init];
//coordinate (for the annotation)
CLLocationCoordinate2D location;
mapAnnotation *ann;
location.latitude=MaB_LATITUDE;
location.longitude=MaB_LONGITUDE;
//annotation
ann = [[mapAnnotation alloc]init];
[ann setCoordinate:location];
ann.title=#"Main Branch";
[annotations addObject:ann];
you dont need to write so much lines of code to show the blue dot , MKMapView itself contains a property called "showUserLocation" which has to be set as true in order to show blue dot on to the Map .
self.mapView.showsUserLocation = TRUE;
It also automatically tracks the Device's Location . Hope it will help you .
Use this if you want show user location on map
self.mapView.showsUserLocation = YES;
I have an app that loads a tabbarcontroller with 3 tabs. One of them is a mapview. It is set to zoom into the user's location by this code:
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
_mapView.showsUserLocation = YES;
CLLocationCoordinate2D zoomLocation;
//IF no city was selected, use userLocation as center
if (!self.cityWasSelected) {
zoomLocation.latitude = self.userLocation.coordinate.latitude;
zoomLocation.longitude = self.userLocation.coordinate.longitude;
CLLocationDistance visibleDistance = 5000; // 5 kilometers
MKCoordinateRegion adjustedRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, visibleDistance, visibleDistance);
[_mapView setRegion:adjustedRegion animated:YES];
} else { //if a city was selected, use that city's value...this is actually the same right now, since self.userLocation is set appropriately elsewhere.
//Set location from selection - forward geocode
zoomLocation.latitude = self.userLocation.coordinate.latitude;
zoomLocation.longitude = self.userLocation.coordinate.longitude;
CLLocationDistance visibleDistance = 5000; // 5 kilometers
MKCoordinateRegion adjustedRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, visibleDistance, visibleDistance);
[_mapView setRegion:adjustedRegion animated:YES];
}
}
The initial tab is a tableviewcontroller for user preferences, the second tab is the mapview and the third is a tableview. When I first tap on the mapview, the map shows the entire world :) If i tap back to the initial tab or the list tab and then return to the mapview, the map is properly centered around my current location.
Why does this happen?
Setting showsUserLocation on the map view will start searching for the user's location. This is an asynchronous operation and you cannot assume that mapView.userLocation will be valid immediately after settings showsUserLocation to YES.
The first time you view appears, you ask the map view to start location. mapView.userLocation probably returns nil. The second time around, the map view has probably finally gotten the user location and loads your map region successfully. This is sheer luck, and you should not rely that you will get location the second time (it may fail, take longer than usual).
When the map view has determined user location, it will call back on a delegate method (MKMapViewDelegate). You need to implement this delegate method like so:
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
// We have location, do your logic of setting the map region here.
...
}
Hope this helps.
I have a tab on the RootViewController.m. The tab has 2 buttons. The first button upon click will go to CorpViewcontroller which has the mapView on it. When I click on the first button on the first try, the map is blank with google label on the bottom. I have to click back then click on the button again then the map show up. Is it possible to always show the map on the first button click?
My rootViewController.m to go to the second screen:
[self.navigationController pushViewController:self.corpController animated:YES];
The second screen called corpViewController has the following code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Set Remote Location";
self.jsonData = [[NSMutableData alloc] init];
mapView.showsUserLocation = YES;
//Setup the double tap gesture for getting the remote location..
UITapGestureRecognizer *tgr = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleGesture:)];
tgr.numberOfTapsRequired = 2;
tgr.numberOfTouchesRequired = 1;
[mapView addGestureRecognizer:tgr];
mapView.delegate = self;
NSLog(#"viewDidLoad done");
}
- (void)viewWillAppear:(BOOL)animated {
NSLog(#"viewWillAppear");
appDelegate = (NBSAppDelegate *)[[UIApplication sharedApplication] delegate];
double curLat = [appDelegate.curLat doubleValue];
MKUserLocation *userLocation = mapView.userLocation;
double miles = 10.0;
double scalingFactor = ABS( (cos(2 * M_PI * curLat / 360.0) ));
MKCoordinateSpan span;
span.latitudeDelta = miles/69.0;
span.longitudeDelta = miles/(scalingFactor * 69.0);
MKCoordinateRegion region2;
region2.span = span;
region2.center = userLocation.coordinate;
[mapView setRegion:region2 animated:YES];
NSLog(#"viewWillAppear done..");
}
Please Advise.
Thank you
Are you initializing the MapView in the viewDidLoad method in your view controller?
If so, try moving it to the viewDidAppear method. That worked for me.
In viewDidLoad you are setting showsUserLocation to YES and in viewWillAppear, you are zooming into the mapView.userLocation coordinate.
The userLocation property isn't usually ready with a valid coordinate immediately after setting showsUserLocation to YES.
The first time you show the view controller, it's still invalid and you are zooming into the coordinate 0,0.
By the time you show the view controller a second time, the user location has been obtained and the coordinate is valid.
Instead of zooming into the user location in viewWillAppear, do it in the delegate method mapView:didUpdateUserLocation: which the map view calls when it gets a user location update.
In addition, you also probably want to move the mapView.showsUserLocation = YES; to viewWillAppear and in viewWillDisappear, set it to NO. This way, the map view will zoom in to the user location every time the view controller is shown instead of just the first time.
An unrelated point is that to zoom in to a specific distance, it's much easier to use the MKCoordinateRegionMakeWithDistance function instead of trying to convert miles to degrees yourself.
Here's an example of the changes suggested in corpViewController:
- (void)viewWillAppear:(BOOL)animated
{
//move this from viewDidLoad to here...
mapView.showsUserLocation = YES;
}
-(void)viewWillDisappear:(BOOL)animated
{
mapView.showsUserLocation = NO;
}
-(void)mapView:(MKMapView *)mv didUpdateUserLocation:(MKUserLocation *)userLocation
//Changed the **internal** parameter name from mapView to mv
//to avoid a compiler warning about it hiding instance var with same name.
//It's better to use the passed parameter variable anyway.
{
NSLog(#"didUpdateUserLocation");
double miles = 10.0;
//Instead of manually calculating span from miles to degrees,
//use MKCoordinateRegionMakeWithDistance function...
//Just need to convert miles to meters.
CLLocationDistance meters = miles * 1609.344;
MKCoordinateRegion region2 = MKCoordinateRegionMakeWithDistance
(userLocation.coordinate, meters, meters);
[mv setRegion:region2 animated:YES];
}
Very much a newbie here so please forgive the ignorance. I have spent some time trying to understand what I am missing but cannot figure it out.
My app centers over Washington State when loading but when I try to zoom to the users current location it puts me at latitude 0 longitude 0. If I comment out the "// startup: center over WA" section it centers over the users current location and then goToLocation works fine.
How do I get it to center over Washington State and then zoom to the users current location upon clicking goToLocation?
Thanks!
- (void)viewDidLoad {
[super viewDidLoad];
[self loadOurAnnotations];
[mapView setShowsUserLocation:NO];
// startup: center over WA
CLLocationCoordinate2D defaultCoordinate;
defaultCoordinate.latitude = 47.517201;
defaultCoordinate.longitude = -120.366211;
[mapView setRegion:MKCoordinateRegionMake(defaultCoordinate, MKCoordinateSpanMake(6.8, 6.8)) animated:NO];
}
-(IBAction)goToLocation {
MKUserLocation *myLocation = [mapView userLocation];
CLLocationCoordinate2D coord = [[myLocation location] coordinate];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(coord, 350, 350);
[mapView setRegion:region animated:YES];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1];
[UIView commitAnimations];
}
First, to use userLocation in the MKMapView at all, you have to pass YES to setShowsUserLocation (not NO).
Next thing is that after turning showsUserLocation on, it may take a few seconds or more for the map view to determine the location and set userLocation. Until then, the location will be nil (giving coordinates of 0,0).
To really know when the userLocation is ready (or updated), implement the didUpdateUserLocation delegate method. It's also helpful to implement the didFailToLocateUserWithError method in case of a problem determining the user location.
However, in your case, you could just do the following in the goToLocation method:
MKUserLocation *myLocation = [mapView userLocation];
if (myLocation.location == nil)
{
NSLog(#"user location has not been determined yet");
return;
}
CLLocationCoordinate2D coord = [[myLocation location] coordinate];
MKCoordinateRegion region = ... //rest of the code stays the same
The animation statements at the end of that method don't do anything, by the way.