Invalid MKMapView region in iOS 5 - iphone

MKMapView returns an invalid region under iOS 5 -- latitude + latitudeDelta/2 is over 100 and shouldn't be over 90.
Has anyone seen this problem?
Steps to Reproduce:
Create an MKMapView
Log the mapView.region from within the regionDidChangeAnimated delegate method
Zoom out the map as far as possible and drag it to the right, so the view is scrolled to the top/left
Expected Results:
In iOS 4, the mapView.region is reasonable:
lat=2.202047 lon=-67.500000 latDelta=165.698164 lonDelta=225.000000
In iOS 5, however, the mapView.region is out of bounds:
lat=17.978733 lon=-67.500000 latDelta=165.698164 lonDelta=225.000000
Latitudes should be within the -90 to 90 range. However, in iOS 5, lat + latDelta/2 is 100.827815. That is not possible. While I can clamp the values at +/- 90, the offset difference is causing problems with our overlays.
Regression:
Does not happen in iOS 4.3. Happens regularly in iOS 5. Screen dumps of the map views look identical even though the center latitude is 15 degrees off.
Notes:
Project file and screen dumps can be downloaded here.

This seems to be an adequate workaround. Rather than reading the mapView.region property, call this method instead:
#implementation MKMapView(fixedRegion)
-(MKCoordinateRegion) fixedRegion_
{
// this call is broken on iOS 5, as is the region property, so don't use them
// return( [self convertRect:self.bounds toRegionFromView:self] );
CLLocationCoordinate2D topLeft = [self convertPoint:CGPointZero toCoordinateFromView:self];
CLLocationCoordinate2D bottomRight = [self convertPoint:CGPointMake(self.bounds.size.width, self.bounds.size.height) toCoordinateFromView:self];
MKCoordinateRegion region;
region.center.latitude = (topLeft.latitude + bottomRight.latitude)/2;
region.center.longitude = (topLeft.longitude + bottomRight.longitude)/2;
region.span.latitudeDelta = fabs( topLeft.latitude - bottomRight.latitude );
region.span.longitudeDelta = fabs( topLeft.longitude - bottomRight.longitude );
return region;
}
#end
Now one could argue (correctly!) that this code isn't 100% correct either because the center value of a Mercator projection in lon/lat isn't really halfway between the top and bottom, but since this matches iOS 4 functionality and keeps the values within the legal range for the map, it works for me.

By using the MKMapView+ZoomLevel category, you won’t have to bother setting the region at all.
here is very good tutorial on the same
http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/
or
http://mayurbirari.wordpress.com/2011/02/07/how-to-access-mkmapkit-in-iphone/
After you perform the zoom/pinch operation try to load the region in
-(void) mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
instead in
regionDidChangeAnimated.
hope this will help..:)

Related

Different map zoom on iPhone 4 and iPhone 5

I am using MapKit for my project and so far it has been very good. Here is a chunk of code I use for displaying and centering the map.
CLLocation *locSouthWest = [[CLLocation alloc] initWithLatitude:southWest.latitude longitude:southWest.longitude];
CLLocation *locNorthEast = [[CLLocation alloc] initWithLatitude:northEast.latitude longitude:northEast.longitude];
CLLocationDistance meters = [locSouthWest distanceFromLocation:locNorthEast];
MKCoordinateRegion region;
region.center.latitude = (southWest.latitude + northEast.latitude) / 2.0;
region.center.longitude = (southWest.longitude + northEast.longitude) / 2.0;
region.span.latitudeDelta = meters / 111319.5;
region.span.longitudeDelta = 0.0;
[self.mapView setRegion:region animated:YES];
The problem is, that it works differently on iPhone 4 and iPhone 5.
Here is iPhone 4 (same results for iOS5 and iOS6):
and here is iPhone 5 (using the same coordinates):
Anybody experiencing the same?
MapKit has fixed zoom levels. Setting the map's region ensures that the region will be visible in the map, but does not set the exact zoom. This has several benefits, the primary one being you can't create a map that scales latitude and longitude disproportionately (leading to a confusing and/or misleading map). The frames of the maps are different sizes, so your selected region can display at different zoom levels on each device.
As an experiment, try setting the frame of the map view to the same size on both devices. Then, if you absolutely need the maps to display at the same scale you could do some math to compute the appropriate region based on the frame of the map.
These are the lines of code that are causing the difference:
region.center.latitude = (southWest.latitude + northEast.latitude) / 2.0;
region.center.longitude = (southWest.longitude + northEast.longitude) / 2.0;
Which are bound to create a difference since the screen sizes vary.
What you can do here to center the map is to use mapView.centerCoordinate

How to show map of within 2.5 Miles from Current Place?

I have MKMapView and I am showing it on viewdidload using following code. However i want to display map of 2.5 Miles Radius when the view loads for the first time and then allow user to zoomin - zoomout.
Let me know how can i set zoom level of 2.5 Miles radius.
Please let me know if I sounds unclear.
Thanks,
Jigar
//[mapListBgView setMapType:MKTYP];
[dataDisMapView setMapType:MKMapTypeSatellite];
[dataDisMapView setZoomEnabled:YES];
[dataDisMapView setScrollEnabled:YES];
MKCoordinateRegion region = { {0.0, 0.0 }, { 0.0, 0.0 } };
region.center.latitude = 25.76 ;
region.center.longitude = -80.19;
[dataDisMapView setRegion:region animated:YES];
Use the MKCoordinateRegionMakeWithDistance() function to create an appropriately sized region. The only thing you have to do is manually convert from miles to meters.

MapKit iPhone display Zoom Controls

Is it possible using MapKit to show a map in iPhone, like i´m doing, to display zoom controls on the map?
If not, what are the flags or methods i should use to increase and diminish the zoom, so i can created methods which will do this at the push of a button.
There aren't any built-in controls for it.
A couple of suggestions:
You can predefine some spans and store them in an array somewhere and then store your position in that array as well. Zooming in/out changes the position and gets the span.
Zooming in takes the current span and divides by 2, zooming out multiplies by 2.
You change the span in the region of the mapView. So you have to:
Get the region of the mapView.
Get the span of the region.
Change the span to what you want.
Set the regions span to the new span.
Set the mapView's region to the new region.
Here's some code for suggestion 2:
MKCoordinateRegion region = mapView.region;
MKCoordinateSpan span;
span.latitudeDelta = region.span.latitudeDelta*2;
span.longitudeDelta = region.span.longitudeDelta*2;
region.span = span;
[mapView setRegion:region animated:TRUE];
For swift 4, this is what I use:
func zoom(_ zoomin : Bool) {
var region = mapView.region;
var span = MKCoordinateSpan();
span.latitudeDelta = zoomin ? region.span.latitudeDelta / 2 : region.span.latitudeDelta * 2;
span.longitudeDelta = zoomin ? region.span.longitudeDelta / 2 : region.span.longitudeDelta * 2;
region.span = span;
mapView.setRegion(region, animated: true);
}
Off the top of my head I don't remember (at work on a windows box) if there is a switch to display the zoom controls. I assume you are talking about displaying the level of zoom, because by default you can pinch/spread the view for zoom.
If there isn't a switch to display the controls, then you will need to create a custom view layer and put it on top of the mapkit view. Then you will need to call the different functions to change the zoom level.
Those functions are all documented in the mapkit docs. Just do a search for MapKit in the documentation center.
Edit:
http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKMapView_Class/MKMapView/MKMapView.html#//apple_ref/doc/uid/TP40008205
According to the docs there isn't a switch to display the controls, but the zoom enabled property lets you turn the ability on and off.
So you can subscribe to the delegate function regionWillChange and use the mapView object to get the zoom level and then set your graphics accordingly.
I don't think you should do that, the expected way to zoom in and out is using the pinch gestures. However if you still want to go ahead and do it, you should place yourself the buttons over the MKMapView, and then with those modify the region property of the MKMapView

MKMapView ignores update of centerOffset in iOS 4

I previously created a custom callout bubble as a subview to the MKAnnotationView because the built in callout is so limited. This requires me to change to centerOffset of the MKAnnotationView when it is selected to account for the size of the callout bubble. This all worked perfectly before iOS 4 came out. Now, with iOS 4, it completely ignores my updating of the centerOffset property and therefore the pin and bubble appear to jump down and to the right (the top left corner of the callout bubble is now at the location where the pin point should be).
Does anyone know why this has changed in iOS 4? Is there something I can do to get the MKMapView to recognize the new centerOffset? Is this a bug that apple introduced?
Thanks for the help!
Make sure you are using MKAnnotationView and not MKPinAnnotationView! You can't set the centerOffset of a MKPinAnnotationView-object (except if you subclass of course).
I have the same problem - centerOffset seems to be taken into account only the first time.
It is changed internally, but the view is not moved - so what you need to do is move the view yourself.
You can move the view by adjusting its center with the required offset - the selected view remains aligned at the top-left corner with the unselected view, so you need to realign their centers. Here's my case:
Selected -> Unselected:
self.center = CGPointMake(self.center.x + 56.0, self.center.y + 130.0);
self.centerOffset = CGPointMake(5.0, -14.0);
Unselected -> Selected:
self.center = CGPointMake(self.center.x - 56.0, self.center.y - 130.0);
self.centerOffset = CGPointMake(64.0, -81.0);
Where 130 is the difference in height between the views(center point is at the bottom), and 56 is the difference between the X offsets of their centers.
Remember - you still need to change the center offset because it'll be taken into account when zooming.
Hope this helps, I've lost a few hours on this. Remember to submit a bug report to Apple.
I think instead of centerOffset you can use setRegion which works fine in all versions.
CGPoint point = [mapView convertCoordinate:selectedAnnotation.coordinate toPointToView:self.view];
CGRect frame = [customView frame];
frame.origin.y = point.y - frame.size.height;
frame.origin.x = point.x - frame.size.width / 2;
MKCoordinateRegion region = [mapView convertRect:frame toRegionFromView:self.view];
[mapView setRegion:region animated:YES];

how can I show compass/heading on mapkit mapview

on the iPhone 3GS in the "Maps" app you can click the icon which usually shows your position twice and the blue dot gains what looks like a beam from a headlamp, basically showing you the direction you are facing on the map and rotating the image accordingly.
Is this option available using MapKit MapView ?
I'm know that I can get my heading with something like
- (void)locationManager:(CLLocationManager *) manager didUpdateHeading:(CLHeading *) newHeading {
// If the accuracy is valid, process the event.
if (newHeading.headingAccuracy > 0) {
CLLocationDirection theHeading = newHeading.magneticHeading;
...
}
}
but I don't know how to get that nice headlamp effect in Mapkit and there doesn't seem to be any documentation.
Any ideas?
Adding user tracking mode also helps. I know I am late, but possibly a help to other developers like me :)
self.mapView.userTrackingMode = RMUserTrackingModeFollowWithHeading;
I found a solution:
I rotate the map using the available heading-information with
[mapView setTransform:CGAffineTransformMakeRotation(heading.magneticHeading * M_PI / -180.0)];
Therefore the "beam" always points to the top of the device. I now just display an ImageView on top of the map and change it's position in locationManager:didUpdateToLocation:fromLocation:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
// scroll to new location
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(newLocation.coordinate, 2000, 2000);
[self.mapView setRegion:region animated:YES];
// set position of "beam" to position of blue dot
self.headingAngleView.center = [self.mapView convertCoordinate:newLocation.coordinate toPointToView:self.view];
// slightly adjust position of beam
self.headingAngleView.frameTop -= self.headingAngleView.frameHeight/2 + 8;
}
Whereby frameTop and frameHeight are shortcuts for frame.origin.y and frame.size.height.
It is not ideal and sometimes lacks a little bit when the dot changes it's position, but I'm happy with the solution b/c it works.
Have a look at my OpenSource framework MTLocation which does this all (and a lot of other cool Map-related stuff for you):
MTLocation
The rotating logic of the other answers are good, however relying on the location manager delegate methods won't result in a good solution. The "beam" image will rarely be in the same place as the blue dot and will bounce around a lot.
The best solution is to get a reference to the blue dot view, and add the "beam" image as a subview to it. I went with a beam image that was a square, with the beam up top and transparency all around it. Imagine the blue dot being in the center of the image.
// An MKMapViewDelegate method. Use this to get a reference to the blue dot annotation view as soon as it gets added
- (void)mapView:(MKMapView *)aMapView didAddAnnotationViews:(NSArray *)views {
for (MKAnnotationView *annotationView in views) {
// Get the user location view, check for all of your custom annotation view classes here
if (![annotationView isKindOfClass:[MKPinAnnotationView class]])
{
self.userLocationAnnotationView = annotationView;
gotUsersLocationView = YES;
}
}
}
// Adds the 'viewPortView' to the annotation view. Assumes we have a reference to the annotation view. Call this before you start listening to for heading events.
- (void)addViewportToUserLocationAnnotationView {
TTDASSERT(self.userLocationAnnotationView != nil);
if (self.userLocationAnnotationView == nil) {
// No reference to the view, can't do anything
return;
}
if (self.viewPortView == nil) {
self.viewPortView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"map_viewport.png"]] autorelease];
}
[self.userLocationAnnotationView addSubview:self.viewPortView];
[self.userLocationAnnotationView sendSubviewToBack:self.viewPortView];
self.viewPortView.frame = CGRectMake((-1 * self.viewPortView.frame.size.width/2) + self.userLocationAnnotationView.frame.size.width/2,
(-1 * self.viewPortView.frame.size.height/2) + self.userLocationAnnotationView.frame.size.height/2,
self.viewPortView.frame.size.width,
self.viewPortView.frame.size.height);
}
I could think of one method, though I have not implemented but may help you a bit.
Firstly you need an image with a pointer of your choice, this pointer must be pointing Upwards 90degree.
Now in your MapKit off the Current Location Marker.
In your didUpdateHeading use the x and y values to calculate the angle of the direction.
Use this angle to rotate the image of the pointer
Use this rotated image as a Annotation Pin in your map.
You would require to update the position of the pointer frequently.
Please post your suggestions/changes for the above approach.
Swift 3 implementation
mapView.userTrackingMode = MKUserTrackingMode.followWithHeading