The scrollEnabled
seems to be breakable once the user starts pinching in a MKMapView.
You still can\'t scroll with one finger, but if y
I did not have a lot of luck with any of these answers. Doing my own pinch just conflicted too much. I was running into cases where the normal zoom would zoom farther in than I could do with my own pinch.
Originally, I tried as the original poster to do something like:
- (void) mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
MKCoordinateRegion region = mapView.region;
//...
// adjust the region.center
//...
mapView.region = region;
}
What I found was that that had no effect. I also discovered through NSLog
s that this method will fire even when I set the region
or centerCoordinate
programmatically. Which led to the question: "Wouldn't the above, if it DID work go infinite?"
So I'm conjecturing and hypothesizing now that while user zoom/scroll/rotate is happening, MapView somehow suppresses or ignores changes to the region. Something about the arbitration renders the programmatic adjustment impotent.
If that's the problem, then maybe the key is to get the region adjustment outside of the regionDidChanged:
notification. AND since any adjustment will trigger another notification, it is important that it be able to determine when not to adjust anymore. This led me to the following implementation (where subject
is supplying the center coordinate that I want to stay in the middle):
- (void) recenterMap {
double latDiff = self.subject.coordinate.latitude self.mapView.centerCoordinate.latitude;
double lonDiff = self.subject.coordinate.longitude - self.mapView.centerCoordinate.longitude;
BOOL latIsDiff = ABS(latDiff) > 0.00001;
BOOL lonIsDiff = ABS(lonDiff) > 0.00001;
if (self.subject.isLocated && (lonIsDiff || latIsDiff)) {
[self.mapView setCenterCoordinate: self.subject.coordinate animated: YES];
}
}
- (void) mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
if (self.isShowingMap) {
if (self.isInEdit) {
self.setLocationButton.hidden = NO;
self.mapEditPrompt.hidden = YES;
}
else {
if (self.subject.isLocated) { // dispatch outside so it will happen after the map view user events are done
dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^{
[self recenterMap];
});
}
}
}
}
The delay where it slides it back can vary, but it really does work pretty well. And lets the map interaction remain Apple-esque while it's happening.
Try implementing –mapView:regionWillChangeAnimated:
or –mapView:regionDidChangeAnimated:
in your map view's delegate so that the map is always centered on your preferred location.
I tried this and it works.
First create a property:
var originalCenter: CLLocationCoordinate2D?
Then in regionWillChangeAnimated, check if this event is caused by a UIPinchGestureRecognizer:
func mapView(mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
let firstView = mapView.subviews.first
if let recognizer = firstView?.gestureRecognizers?.filter({ $0.state == .Began || $0.state == .Ended }).first as? UIPinchGestureRecognizer {
if recognizer.scale != 1.0 {
originalCenter = mapView.region.center
}
}
}
Then in regionDidChangeAnimated, return to original region if a pinch gesture caused the region changing:
func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if let center = originalCenter {
mapView.setRegion(MKCoordinateRegion(center: center, span: mapView.region.span), animated: true)
originalCenter = nil
return
}
// your other code
}
You can try to handle the pinch gestures yourself using a UIPinchGestureRecognizer
:
First set scrollEnabled
and zoomEnabled
to NO
and create the gesture recognizer:
UIPinchGestureRecognizer* recognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self
action:@selector(handlePinch:)];
[self.mapView addGestureRecognizer:recognizer];
In the recognizer handler adjust the MKCoordinateSpan
according to the zoom scale:
- (void)handlePinch:(UIPinchGestureRecognizer*)recognizer
{
static MKCoordinateRegion originalRegion;
if (recognizer.state == UIGestureRecognizerStateBegan) {
originalRegion = self.mapView.region;
}
double latdelta = originalRegion.span.latitudeDelta / recognizer.scale;
double londelta = originalRegion.span.longitudeDelta / recognizer.scale;
// TODO: set these constants to appropriate values to set max/min zoomscale
latdelta = MAX(MIN(latdelta, 80), 0.02);
londelta = MAX(MIN(londelta, 80), 0.02);
MKCoordinateSpan span = MKCoordinateSpanMake(latdelta, londelta);
[self.mapView setRegion:MKCoordinateRegionMake(originalRegion.center, span) animated:YES];
}
This may not work perfectly like Apple's implementation but it should solve your issue.
I've read about this before, though I've never actually tried it. Have a look at this article about a MKMapView with boundaries. It uses two delegate methods to check if the view has been scrolled by the user.
http://blog.jamgraham.com/blog/2012/04/29/adding-boundaries-to-mkmapview
The article describes an approach which is similar to what you've tried, so, sorry if you've already stumbled upon it.
Swift 3.0 version of @Paras Joshi answer https://stackoverflow.com/a/11954355/3754976
with small animation fix.
class MapViewZoomCenter: MKMapView {
var originalRegion: MKCoordinateRegion!
override func awakeFromNib() {
self.configureView()
}
func configureView() {
isZoomEnabled = false
self.registerZoomGesture()
}
///Register zoom gesture
func registerZoomGesture() {
let recognizer = UIPinchGestureRecognizer(target: self, action:#selector(MapViewZoomCenter.handleMapPinch(recognizer:)))
self.addGestureRecognizer(recognizer)
}
///Zoom in/out map
func handleMapPinch(recognizer: UIPinchGestureRecognizer) {
if (recognizer.state == .began) {
self.originalRegion = self.region;
}
var latdelta: Double = originalRegion.span.latitudeDelta / Double(recognizer.scale)
var londelta: Double = originalRegion.span.longitudeDelta / Double(recognizer.scale)
//set these constants to appropriate values to set max/min zoomscale
latdelta = max(min(latdelta, 80), 0.02);
londelta = max(min(londelta, 80), 0.02);
let span = MKCoordinateSpanMake(latdelta, londelta)
self.setRegion(MKCoordinateRegionMake(originalRegion.center, span), animated: false)
}
}