How do I intercept mapview panning and zooming (Swift 4) - ios

I'm trying to restrict panning and zooming for a mapview, to remain within a predefined max and min camera altitude and the four corners of a bounding box.
The solutions proposed here, allow the user to pan outside the region, and then use regionDidChangeAnimated to pan them back to within the pre-defined bounds.
I'd like to know if it's possible to just never allow the user to pan outside the bounds in the first place. Can I, for example, intercept panning/zooming gestures and decide whether or not to let them continue?
I think I'm looking for something like the following pseudocode:
func blockMapViewPanEvent(_ mapView: MKMapView){
return mapView.visibleMapRect.isContainedWithin(topLeft, bottomRight)
}
I know that MapBox's Swift SDK achieves this, so I imagine it's possible. Can I do this with plain-old MapKit?
Edit for Clarity:
I want to restrict panning and zooming so the user can pan and zoom as much as they want within a bounding box. For example, if the bounding box is of Germany, I want the user to be able to pan and zoom within Germany, but they should never be able to see Spain, since Spain is outside the bounding box of Germany.
I do not want to disable all user interaction on the map view.

If I am understanding you correctly, you want to disable user interaction in the map.
Here are the few options available in map view. These might be helpful for you
mapView.zoomEnabled = false
mapView.scrollEnabled = false
mapView.userInteractionEnabled = false
If you have issues with these you can simply put a transparent view over map view

MapKit now makes this possible in iOS 13
You can restrict panning with:
let boundaryRegion = MKCoordinateRegion(...) // the region you want to restrict
let cameraBoundary = CameraBoundary(region: boundaryRegion)
mapView.setCameraBoundary(cameraBoundary: cameraBoundary, animated: true)
And prevent the user zooming outside your region of interest with:
let zoomRange = CameraZoomRange(minCenterCoordinateDistance: 100,
maxCenterCoordinateDistance: 500)
mapView.setCameraZoomRange(zoomRange, animated: true)
There are some limitations that you can best see in the WWDC 2019 video at 2378 seconds.
References
MapKit documentation on "Constraining the Map View"
class reference for MKMapView.CameraBoundary
class reference for MKMapView.CameraZoomRange

Related

A way to pass gestures below an overlay view in Swift, depending on the input gesture type

I am developing an application, which has a map as its core feature. On this map, users can draw and edit polygons, lines, and add points of interest (POIs). To edit a polygon, for instance, one should tap on it, and the application would enter an editing mode after that.
To accomplish this kind of behaviour, I have a transparent overlay view (UIView), that lays just above the map. This view can either ‘capture’ user’s gesture (i.e. tap, long press, etc), if it hits an area of screen, that contains a polygon, or pass it down to the map, if there is no polygon met at the point of the tap. This behaviour is achieved by overriding a UIView method point(inside:with:) (docs here). The pseudocode for the implementation goes like this:
override public func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if containsPolygongs(at: point) {
return true
} else {
return false
}
}
However, I have an edge-case that got me stuck a bit. Additionally to previously described behaviour, I want to be able to move the map when I put my finger down at the polygon and start sliding my finger around. Or the same with pinch-to-zoom behaviour. So, basically, depending on the type of gesture, I want my point(inside:with:) to return either true (for Tap gesture), or false (for Pan/Pinch gesture). Or, speaking more generically, I want my view to be ‘physically present’ when I tap on a polygon, and ‘physically absent’ when the received gesture is anything but a tap.
P. S.: I am not sure that my idea with point method is 100% correct and is a single possible one way of accomplishing this behaviour. Maybe there is a way to capture all the gestures and dispatch them to the view that lays below. Any idea is great as long as it works. Thanks!

Moving Map Under the Marker in Mapbox iOS SDK

I am new in MapBox iOS SDK and I need to add a marker in the center of MGLMapView so that user would be able to move map view under the marker and the marker would be fix on the screen. I also need to get the coordinate of the point in the map that is under the marker. I could not find any method in Mapbox SDK and I have no idea how to do that.
I believe this is quite easy. Add an image of a marker on top of the map to give the visual effect so the user can still scroll around without moving the marker. Then you can get the center coordinates usually quite easily using mapView.centerCoordinate when the user stops scrolling.
Here is the API documentation link for reference
I had done something like this in one of my apps
1) Add map to UIViewController
2) Add a Transparent view on top of the map in UIViewController. (May need to set userInteractionEnabled to false. Not sure though!)
3) Add marker image to the Transparent view so that its bottom tip is at the center of the view it is added to.
4) Get center coordinates by using mapView.centerCoordinate
For other people who have this issue too:
let markerImageView = UIImageView(image: UIImage(named: "Orange"))
markerImageView.center = mapView.center
mapView.addSubview(markerImageView)

How to hide the map (or make it a unified color), while keeping the pins on top?

I have a traditional map from mapKit with a set of pins at their respective location. I am trying to keep the pins at the same place, while making the map invisible.
Is there any way to do this?
Thank you,
OK, I have to admit this one made me really curious! I don't know of a clean way of doing what you want, although I suspect there might be interesting hacks you can do around adding overlays.
One solution you might want to experiment with is to use viewForAnnotation() to pull out the location of each pin annotation on the map, transform that to your view, then add custom views in its place. You can then hide the entire map (which would hide its pins), leaving your fake pins in place.
Off the top of my head, it would look something like this:
let annotations = mapView.annotations
for annotation in annotations {
if let annotationView = mapView.viewForAnnotation(annotation) {
let transformedFrame = view.convertRect(annotationView.frame, fromView: annotationView.superview)
let testView = UIView(frame: transformedFrame)
testView.backgroundColor = UIColor.redColor()
view.addSubview(testView)
}
}
You will have problems when the map view moves, though, so you might need to keep track of your fake pins and move them appropriately.

How to add custom floor plan for MapView with zoom and pan

I am developing an iOS app using swift. I need to show a custom floor plan which users can zoom and pan. Floor plan is an image which I need to set to a mapview instead of showing a world map. When zooming it should load images for particular levels. And also I have to show some pin pointers for some points. I do not want to show the current location.
MKMapView is the best option? Or is there anything else to achieve this?
You can use this example for start. It shows how to position floor plan on map. So you'll require in addition to implement delegates methods - func mapView(mapView:MKMapView regionDidChangeAnimated animated:Bool) or func mapView(mapView:MKMapView regionWillChangeAnimated animated:Bool) to determine current zoom level to change your image visible part (as soon as it's a tiled image).

A better way to mark location on a map

I'm making an iOS app, where I want users to mark locations on a regular basis on a map. The default way to mark location in iOS is to drop a pin either at the center of the map or where the user holds a touch (using the gesture recognizer).
Then to mark the pin to the desired location ACCURATELY, the user holds the pin and can drop it again. If you have done this on an iOS maps app, you will know it is hard to mark a location ACCURATELY, in this manner. It looks cool, but takes some trial and error in my opinion.
If I can help it, I want to make this process smoother and more obvious is my app.
So is there any other way for the user to mark a location on a map, without doing the default pin drag-drop? Any Suggestions welcome :-)
Just an idea... instead of moving the pin, you could move the map with the finger, and keep the pin always in the middle of the mapView (maybe marked with a crosshair or something). Then you can get the coordinates of the Pin using the "center" method of the mapView. That way you don't cover the pin / crosshair with your finger.
I recommend you have a look at the Maps app again. When you hold down your finger on a dropped pin, notice the location of bottom of the pin on the map. The pin actually drops in this exact location, after doing a quick bounce. The pin also sits about 20 above the users finger.
While it's not the most obvious, it's very accurate in my opinion. Enough to query the Geocoder API for an address, or just get coordinates. Keep in mind the GPS has an accuracy of 5-10 meters at the most. Zooming in will allow the user to go even more accurate.
But if this solutions isn't what you want, I'd overlay a point in the centre of the map and make sure it doesn't respond to touches, making it possible to move around the map underneath this point. When touched end, then drop a pin in the middle of the map where the point is. If touches begin again, pop out the pin from the map. I don't think a pin that's already on the map staying in place while the map moves independently will look good.

Resources