MKMapView - Limit map scroll to a square overlay - ios

I have a MKMapView with a square overlay described as:
CLLocationCoordinate2D coordsBg[5]={
CLLocationCoordinate2DMake(31.750865,35.180882),
CLLocationCoordinate2DMake(31.740331,35.180882),
CLLocationCoordinate2DMake(31.740331,35.165452),
CLLocationCoordinate2DMake(31.750865,35.165452),
CLLocationCoordinate2DMake(31.750865,35.180882)
};
MKPolygon *bg=[MKPolygon polygonWithCoordinates:coordsBg count:5];
[map addOverlay:bg];
I wish to limit the user from scrolling outside of the overlay.
Can I limit the MKMapView scroll view for that? Or there is an other method?
Thanks
Shani

After Few hours of banging my head, I got to this solution that work great for me.
It mixes up :
This post: set the zoom level of an mkmapview
And This link: restrict mkmapview scrolling from #Anna Karenina comment to my question.
And This is my code:
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
{
lastGoodMapRect = mapView.visibleMapRect;
lastGoodRegion = mapView.region;
}
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
if (manuallyChangingMapRect) {
manuallyChangingMapRect=NO;
return;
}
if( [mapView zoomLevel] > 16 )
{
[mapView setCenterCoordinate:lastGoodRegion.center zoomLevel:16 animated:YES];
}
MKMapRect visibleRect = mapView.visibleMapRect;
MKMapRect OverlayRect = bg.boundingMapRect;
MKMapRect intersectionRect = MKMapRectIntersection(visibleRect,OverlayRect);
//you can change the min and max zoom off course
if(!MKMapRectEqualToRect(visibleRect,intersectionRect)){
if( [mapView zoomLevel] < 15){
[mapView setCenterCoordinate:lastGoodRegion.center zoomLevel:15 animated:YES];
}else if( [mapView zoomLevel] > 16 )
{
[mapView setCenterCoordinate:lastGoodRegion.center zoomLevel:16 animated:YES];
}else{
manuallyChangingMapRect=YES;
[mapView setVisibleMapRect:lastGoodMapRect animated:YES];
}
}
}

Related

viewForAnnotation didn't moves the userLocation

I've implemented - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation { method to change the default location of userLocation Annotation. Inside this method I've used:
if (annotation==(mapView.userLocation)) {
MKAnnotationView *myAnotation = [[MKAnnotationView alloc] init];
myAnotation.image = self.userPointImage.image;
myAnotation.centerOffset = CGPointMake(0, 250);
return myAnotation;
}
else
{
return nil;
}
Which successfully moves the default userLocation annotation to center bottom. But as I'm also using heading so when move my phone it is still rotating the map from center.
- (void)locationManager:(CLLocationManager *) manager didUpdateHeading:(CLHeading *) newHeading {
[self.mapView setUserTrackingMode:MKUserTrackingModeFollowWithHeading animated:true];
}
But the userLocation annotation is below. So how to do that?
UPDATE I found this question and tried applying it. But as I'm using heading so it crashes the app.
In my didUpdateUserLocation method I added [self moveCenterByOffset:CGPointMake(0, 200) from:userLocation.coordinate]; and this method is as follows:
- (CLLocationCoordinate2D)moveCenterByOffset:(CGPoint)offset from:(CLLocationCoordinate2D)coordinate
{
CGPoint point = [self.mapView convertCoordinate:coordinate toPointToView:self.mapView];
point.x += offset.x;
point.y += offset.y;
CLLocationCoordinate2D center = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
return center;
}
and then in my didUpdateUserLocation method I updated it as: MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance([self moveCenterByOffset:CGPointMake(0, -250) from:userLocation.coordinate], 800.0f, 200.0f); and [mapView setRegion:region animated:NO]; as I'm using heading so animation is set to NO. But now as I run the code it starts jerking between center and the new point.
MKCoordinateRegion region = mapClinicsView.region;
set 'region.center'
It will helps you to navigate your map center position
make sure 'region.center' and annotation center latitute and longitute is same

How to make a MKMapView come in from the top

I have a normal MKMap view with a basic MKAnnoation what i want to accomplish is that my annotation is in the UK and the MKCoordinateSpan is 0.05 but i want the MKMapView start from a view of the whole UK and zoom in to the annotation.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
/* set here the region from where map will be zooming; */
[self.mapView setRegion:MKCoordinateRegionForMapRect(MKMapRectWorld) animated:NO];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
/* set here your end point of zooming */
CLLocationCoordinate2D zoomLocation;
zoomLocation.latitude = 39.281516;
zoomLocation.longitude= -76.580806;
[self.mapView setCenterCoordinate:zoomLocation animated:YES];
CLLocationDistance locationDistance = [self locationDistance:0.5];
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, locationDistance, locationDistance);
[self.mapView setRegion:viewRegion animated:YES];
}
- (CLLocationDistance )locationDistance:(CGFloat )value {
return 1609.344 *value;
}

Detect touch nearby to CLLocationCoordinate2D in pixels

I have few GMSMarker on GMSMapView, all of them are draggable, so when I longpress them, I can move them around the map. However I have also an action on longpress on GMSMapView, which adds a marker.
- (void)mapView:(GMSMapView *)mapView didBeginDraggingMarker:(GMSMarker *)marker {
self.moving = YES;
}
- (void)mapView:(GMSMapView *)mapView didEndDraggingMarker:(GMSMarker *)marker {
self.moving = NO;
}
- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate {
if (self.moving) {
return;
}
[self addMarkerAtCoordinate:coordinate];
}
Now the problem is, that sometimes user mistap and instead of moving marker he adds a new one. Because of this, I'd like to add small area around marker, where user can't add new markers. I've thought about something like this:
- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate {
CGFloat zoomFactor = 35.f - self.mapView.camera.zoom;
CLLocation *location = [[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];
for (GMSMarker *marker in self.markers) {
CLLocation *sectorLocation = [[CLLocation alloc] initWithLatitude:marker.position.latitude longitude:marker.position.longitude];
if ([location distanceFromLocation:sectorLocation] < zoomFactor) {
return;
}
}
}
But of course I don't like this solution, because the area is changing with changed zoom. I'd like something like a finger width around marker to be longpress banned. How to calculate this distance?
It's easy to convert coordinate to location on view using method pointForCoordinate: on GMSProjection GMSMapView object.
- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate {
CGPoint longpressPoint = [mapView.projection pointForCoordinate:coordinate];
for (GMSMarker *marker in self.markers) {
CLLocationCoordinate2D markerCoordinate = marker.position;
CGPoint sectorPoint = [mapView.projection pointForCoordinate:markerCoordinate];
if (fabsf(longpressPoint.x - markerCoordinate.x) < 30.f && fabsf(longpressPoint.y - markerCoordinate.y) < 30.f) { // 30.f ofc should be defined as constant
// handle situation when touchpoint is too close to marker
}
}
}

MKMapView: annotation's callout not shown when setting region

I show several custom annotation views on an MKMapView, and when user taps one of them, I change the region to be centered on it:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
if ([view.annotation isKindOfClass:[CustomAnnotation class]]) {
double miles = 2.5;
double scalingFactor = ABS(cos(2 * M_PI * lat / 360.0));
MKCoordinateSpan span;
span.latitudeDelta = miles/69.0;
span.longitudeDelta = miles/(scalingFactor * 69.0);
MKCoordinateRegion region;
region.span = span;
region.center = self.coordinates;
[self.mapView setRegion:region animated:YES];
}
}
I see that, when the annotation already was shown, more or less, in the center of the map, the callout is displayed, but it is not when I tap an annotation closer to the map's border and the region has to "move more". I tried to select the annotation after the region setting animation ends:
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
for (id annotation in self.mapView.annotations) {
if ([annotation isKindOfClass:[CustomAnnotation class]]) {
if (((CustomAnnotation *)annotation).id == self.selectedAnnotation.id) {
[self.mapView selectAnnotation:annotation animated:YES];
}
}
}
}
But the callout is neither shown. If I don't change programmatically the region when tapping, all callouts are shown. How could I solve this?
Thanks

Incorrect user location shown on MKMapView after scaling the map programmatically

I have MKMapView which have UserTrackingMode = MKUserTrackingModeFollow,
and I have adding a circle overlay to show a region of the certain diameter at user location.
Also user can change the diameter of the region so I want to scale the map to insure whole region/circle is shown on that portion of the map.
The problem I have now is that scaling the map number of times by setting region results in incorrect user location annotation - it is moved from the correct location.
I cannot understand why is that happens, I see in the debugger that the mapView.userLocation property have correct coordinates.
But once new update is happens or I move the map manually - the annotation jumps to the correct place.
This is the code:
- (void)addCircleWithRadius:(double)radius andCoordinate:(CLLocationCoordinate2D)coordinate
{
_regionCircle = [MKCircle circleWithCenterCoordinate:coordinate radius:radius];
[_regionCircle setTitle:#"Region"];
MKCoordinateRegion region;
region.center.latitude = coordinate.latitude;
region.center.longitude = coordinate.longitude;
region.span.latitudeDelta = 0.00002 * _regionCircle.radius;
region.span.longitudeDelta = 0.00002 * _regionCircle.radius;
MKCoordinateRegion adjustedRegion = [self.mapView regionThatFits: region];
[self.mapView setRegion:adjustedRegion animated:TRUE];
_circleView = nil;
[self.mapView addOverlay:_regionCircle];
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
if(!_circleView)
{
_circleView = [[MKCircleView alloc] initWithCircle:overlay];
_circleView.strokeColor = [UIColor blueColor];;
_circleView.fillColor = [UIColor blueColor];
_circleView.alpha = 0.25;
_circleView.lineWidth = 2.0;
}
return _circleView;
}
- (IBAction)regionSliderValueChanged:(id)sender
{
[self updateRadiusCircle];
}
- (void) updateRadiusCircle
{
[self.mapView removeOverlays:self.mapView.overlays];
CLLocationCoordinate2D myCoordinate = {_currentLocation.coordinate.latitude, _currentLocation.coordinate.longitude};
[self addCircleWithRadius:self.radiusSlider.value andCoordinate:myCoordinate];
}
I have published the video on YouTube to better understand the issue.
https://www.youtube.com/watch?v=474gdjkGwJA

Resources