How do i restrict panning and restrict zooming out of a certain area of apple maps in MapKit? - ios

I am currently making an app with a map that should focus on a certain location only. I would like the user to not be able to zoom out or pan out of this area so they can keep their focus on the image overlay that i have put over this area.
In order to get the app to start off from the location that i want and not some random map, I used a tutorial from Ray Wenderlich: https://www.raywenderlich.com/425-mapkit-tutorial-overlay-views
How would I acoomplish my task based on the code that is written in the tutorial above? I have completed the tutorial, so I am looking for help in adding any code and identifying where and what kind of code to put.
I found other tutorials on this topic unhelpful because they were for other map types like Google maps or MapBox. The apple website about MapKit and MaximumZ does not help me very much either.
I am a beginner in XCode and Swift, and have only had little bit of experience in Python previously. I was hoping limiting the zoom and user access to parts of the maps would be easier...
override func viewDidLoad() {
super.viewDidLoad()
let latDelta = park.overlayTopLeftCoordinate.latitude -
park.overlayBottomRightCoordinate.latitude
// Think of a span as a tv size, measure from one corner to another
let span = MKCoordinateSpanMake(fabs(latDelta), 0.0)
let region = MKCoordinateRegionMake(park.midCoordinate, span)
mapView.region = region
}
This is what I have so far for getting the app to startup on the location that I want, using a rectangle that bounds the area that I am looking to restrict the user to.

The MKMapView has a delegate MKMapViewDelegate. This protocol has a function called:
func mapViewDidChangeVisibleRegion(_:)
This method is called whenever the user scrolls or zooms the map. In this method you can specify the behavior of the map. You can, for instance set a specific region that you want the map to zoom into and specify the maximum level of zoom allowed.
In the function mapViewDidChangeVisibleRegion(_:) you can then check to what latitudeDelta and longitudeDelta the map can zoom into. If the delta's go below or above a certain level you can lock the zooming by setting the region with something like this:
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
if mapView.region.span.latitudeDelta < 0.4 {
regionSpan = MKCoordinateSpan(latitudeDelta: 0.4, longitudeDelta: 0.5)
let mapRegion = MKCoordinateRegion(center: self.centerCoordinate, span: self.regionSpan)
self.trackMapView.setRegion(mapRegion, animated: true)
}
}

Related

Change MKMapView's Zoom - Swift 4

I using Apple's MapKit and MKMapView to show a location on screen. The function I am using is:
func displayLocation() {
locationMap.setRegion(MKCoordinateRegion(center: CLLocationCoordinate2DMake(userConnected.siteConnectedLat!, userConnected.siteConnectedLong!), span: MKCoordinateSpanMake(0.05, 0.05)), animated: true)
let locationPin = CLLocationCoordinate2D(latitude: userConnected.siteConnectedLat!, longitude: userConnected.siteConnectedLong!)
let annotation = MKPointAnnotation()
annotation.coordinate = locationPin
locationMap.addAnnotation(annotation)
locationMap.showAnnotations([annotation], animated: true)
}
Is is then called in the viewDidLoad method of the View Controller. Apple's documentation says that the span should change the zoom but even changing the span is putting minimum effect on the map. I want it to be zoomed out enough so that we can clearly see like 3-4 European countries, i.e, a significant amount of zoom-out.
This is what my map looks like in simulator:
This is how it looks like after I press enlarge:
You need to set the region of the map in viewDidAppear, not viewDidLoad. When viewDidLoad is called, the map view has just been loaded - the map hasn't been rendered yet, so you can't set its region.
Another thing to change is the span. (0.05, 0.05) seems too small a span to show 3-4 countries. You should try something bigger, like (5, 5). Remember that these numbers represent in degrees the width and height of the map region.

How to move annotation with animation in mapbox map?

I am using mapbox map in my iOS app. I added annotation in mapbox map by using this class MGLPointAnnotation.
Now the problem is i am unable to change its location. This is how i changing annotation location. But it is not moving. I need to remove first old one and again need to add new annotation but i don't want to do this.Please let me know how can i do this.
car.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lng)
I want the movement of annotation when location changed of annotation with smooth animation.
Also when there is a change in location i'm changing center of my mapbox map with camera. Let me know i am doing right or not.
// Optionally set a starting point.
bMbMapView.setCenter(cord, zoomLevel: 7, direction: bearing, animated: true)
bMbMapView.setDirection(bearing, animated: true)
// Create a camera that rotates around the same center point, rotating 180°.
// `fromDistance:` is meters above mean sea level that an eye would have to be in order to see what the map view is showing.
let camera = MGLMapCamera(lookingAtCenter: cord, fromDistance: 1000, pitch: 7, heading: 90)
// Animate the camera movement over 5 seconds.
bMbMapView.setCamera(camera, withDuration: 1, animationTimingFunction: CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut))
A MGLAnnotationView is a UIView, so you should be able to animate changes with Core Animation: https://github.com/mapbox/mapbox-gl-native/issues/8378#issuecomment-287910768
Another option is to use a style source and layer, then update the source using a timer. This would likely be less smooth than using an animated annotation view.

How do I achieve the maximum zoom level in iOS mapKit?

I'm aware of this post: How to get to the max zoomlevel on iOS MKMapView
And it seems to be exactly what I need to solve the problem, however, the technique does not work for me.
If I set the eyeAltitude at 50, or even 100, I get the same map zoom returned. However, once the map is displayed, I can pinch to zoom to the desired zoom level. is there a solution to programmatically achieve the pinched in zoom level at the initial load of the map view?
EDIT: I've tried using setRegion but the problem seems to be related to using .satelliteFlyover specifically. I need to use .satelliteFlyover due to the difference in image quality.
Further EDIT: I've also discovered that the simulator seems to give a different 'native zoom' than when it runs on my device. I hope someone reads this that can provide insight!!
the code I'm using is:
let greenLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(CLLocationDegrees(greenX),CLLocationDegrees(greenY))
let altitude:CLLocationDistance = 80
self.greenMap.mapType = .satelliteFlyover
let camera = MKMapCamera(lookingAtCenter: greenLocation, fromEyeCoordinate: greenLocation, eyeAltitude: altitude)
self.greenMap.setCamera(camera, animated: true)
You can set region to make map view scale to fit. Use this function.
open func setRegion(_ region: MKCoordinateRegion, animated: Bool)
Here is the sample code.
let map = MKMapView()
let center = CLLocationCoordinate2D(latitude: 115, longitude: 24)
let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003)
let region = MKCoordinateRegion(center: center, span: span)
map.setRegion(region, animated: false)
I solved this by... using a different SDK for mapping. What I found through continued testing is that appleMaps doesn't always have the imagery down to the specific zoom level, and seems to get tripped up when you zoom in ultra close. on some locations it would zoom perfectly, and others it would not - an inconsistency that as a developer you may want to be weary of. googleMap had higher quality imagery and no issues with zoom consistency.

MapView with Polyline or Annotation losing tile on zoom

I have a MKMapView in an IOS app using cached local tiles that works great. Can zoom, move around, etc...
When, however, I add either annotation or a polyline, it still works great until zoom gets to a certain zoom level, then the tiles under the annotations and polylines don't show up, but all others do fine.
zoomed out at the right level
Zoomed in one two many levels.
If I remove the annotations/lines, the map zooms in correctly and works great for the area the annotations/lines would have been in.
Any ideas?
I reduced this to the smallest test case. This runs fine until you zoom in, then any tiles under the polyline disappear. Zoom out and they re-appear.
import Foundation
import UIKit
import MapKit
class MyController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var overlay:MKTileOverlay = MKTileOverlay(URLTemplate: "https://services.arcgisonline.com/ArcGIS/rest/services/USA_Topo_Maps/MapServer/tile/{z}/{y}/{x}.jpg");
override func viewDidLoad() {
mapView.delegate = self
mapView.showsUserLocation = true;
overlay.maximumZ = 15;
overlay.minimumZ = 12;
overlay.canReplaceMapContent = true
mapView.addOverlay(overlay)
var points: [CLLocationCoordinate2D] = [CLLocationCoordinate2D]()
points.append(CLLocationCoordinate2D(latitude: 40.7608, longitude: -111.8910));
points.append(CLLocationCoordinate2D(latitude: 40.8894, longitude: -111.8808));
var polyline = MKPolyline(coordinates: &points, count: points.count)
mapView.addOverlay(polyline)
let region = MKCoordinateRegion(center: points[0], span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
mapView.setRegion(region, animated: false)
}
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if overlay is MKPolyline {
var polylineRenderer = MKPolylineRenderer(overlay: overlay)
polylineRenderer.strokeColor = UIColor.blueColor()
polylineRenderer.lineWidth = 5
return polylineRenderer
} else if (overlay is MKTileOverlay) {
let renderr = MKTileOverlayRenderer(overlay: overlay)
return renderr
}
return nil
}
}
I see that it's been three months since you posed this question, but in case it's still of interest I'll share what I've found.
I've added the capability to download map tiles from Apple using an MKMapSnapshotter and store them into a set of tiles directories for use when you're out of cellular service areas, which works pretty well. The directories cover 3 to 6 different zoom levels depending on a user selection, with the tile images arranged in sub-directories under directories named after their zoom levels, per the iOS standard.
When displaying these using an MKTileOverlayRenderer, it switches appropriately among the images for zoom levels between the min and max zoom levels specified in the MKMapRectMake that I used to set up for the MKTileOverlay object.
The map displays all of the tiles appropriately at all of the specified zoom levels, repainting the tiles as sizes change. When the display goes above the max or below the min zoom levels, it continues to display the tiles from either the max or the min zoom levels for a while since it has no tile images for the new sizes. This works OK when there are no polylines, but the system redraws the squares that lie under polylines to resize the lines and when it does so it clears the tile images and looks for the tiles for the new zoom level. Since it doesn't have those tiles, those squares wind up empty on the tiles level, though squares not overlapped by polylines are not cleared and so retain the old images.
BTW, you may have already run into this but in case you haven't, you need to subclass MKTileOverlay so that you can set the location and size of the area mapped by the tiles.
Hope all of this helps explain things, even if it doesn't necessarily solve the problem of what you'll want to do about it.

MKMapView Doesn't Show Whole World At Once

I'm working on an application that tracks movement in Swift, for traveling from point A: locationOne, to point B: locationTwo. The MapView should display both points on the map, centered in between them.
I implemented a function to determine the center location as per this link, though I had to modify it to function in Swift. The function is called findCenterPoint.
Then, I set the mapView's region to an MKCoordinateRegion. This region is created like so: let region = MKCoordinateRegionMakeWithDistance(center, 2*distanceOne, 2*distanceTwo), and then the mapView's region is set like this: mapView.setRegion(region, animated: false)
I multiply the distance by 2 so that we have a margin on the sides of the two locations (the annotation for locationOne, and locationTwo: the user location)
Here's the problem: If the two points are very far away, i.e. New York and somewhere in Australia (let's just say general Australia) the app can't display both points, because they don't fit on the mapView. So instead, only one pin is visible, because the other one is off the screen.
Screenshots of problem
I need both of those points to show up without scrolling around the map.
I also have a degreesToRadians function which is used in the code below.
TL;DR: My app's mapView isn't big enough to fit and display two far away points on the map, and it is already zoomed out to the max.
Here's the actual code:
var center = findCenterPoint(firstLocation.coordinate, locTwo: placemark.coordinate)
let earthRadius: Double = 6371000
distance = degreesToRadians(placemark.coordinate.latitude - firstLocation.coordinate.latitude)
lonDistance = degreesToRadians(placemark.coordinate.longitude - firstLocation.coordinate.longitude)
let region = MKCoordinateRegionMakeWithDistance(center, earthRadius*distance, earthRadius*lonDistance)
mapView.setRegion(region, animated: false)

Resources