MKMapKit draggable annotation and drawing polygons - ios

I am currently trying to allow the user to add pins to the map which will then draw a polygon connecting those pins. However I want to extend it to allow the user to be able to drag the pins and the polygons will be updated accordingly. MKMapView draws the polygon from the array of coordinates according to their arrangement in the array (if I am not mistaken). The problem I am facing now is how do I update the polygons after the user repositioned the pins.
var touchCoordinatesWithOrder: [(coordinate: CLLocationCoordinate2D, order: Int)] = []
var counter = 0
func addLongPressGesture() {
let longPressRecogniser = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))
longPressRecogniser.minimumPressDuration = 1.0
mapView.addGestureRecognizer(longPressRecogniser)
}
func handleLongPress(gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer.state != .Began {
return
}
let touchPoint = gestureRecognizer.locationInView(self.mapView)
let touchMapCoordinate = mapView.convertPoint(touchPoint, toCoordinateFromView: mapView)
let annotation = MKPointAnnotation()
annotation.coordinate = touchMapCoordinate
mapView.addAnnotation(annotation)
touchCoordinatesWithOrder.append((coordinate: touchMapCoordinate, order: counter))
counter += 1
}
#IBAction func drawAction(sender: AnyObject) {
if touchCoordinatesWithOrder.count <= 2 {
print("Not enough coordinates")
return
}
var coords = [CLLocationCoordinate2D]()
for i in 0..<touchCoordinatesWithOrder.count {
coords.append(touchCoordinatesWithOrder[i].coordinate)
}
let polygon = MKPolygon(coordinates: &coords, count: coords.count)
mapView.addOverlay(polygon)
counter = 0
}
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
// if the user repositioned pin number2 then how to I update my array?
}
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKPolygon {
let polygonView = MKPolygonRenderer(overlay: overlay)
polygonView.strokeColor = UIColor.blackColor()
polygonView.lineWidth = 0.5
return polygonView
}
return MKPolylineRenderer()
}

To make the pins draggable, you need to set draggable = true on the MKAnnotationView. Implement the viewForAnnotation and dequeue or create the annotation, then set draggable = true. Ensure that the MKMapView delegate is set otherwise none of the delegate methods will be called.
You may also find it easier to store the annotations in an array, rather than just storing the coordinates. The map view retains a reference to the annotations in the array, so when the point is moved in the map, the annotation is automatically updated.
Your question did not say whether you need to draw a path around the points, or through the points. If you want to draw an overlay which surrounds the points, then you also need to calculate the convex hull for the coordinates. The code example does this, although it's easily removed.
Example:
class MapAnnotationsOverlayViewController: UIViewController, MKMapViewDelegate {
#IBOutlet var mapView: MKMapView!
// Array of annotations - modified when the points are changed.
var annotations = [MKPointAnnotation]()
// Current polygon displayed in the overlay.
var polygon: MKPolygon?
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
addLongPressGesture()
}
func addLongPressGesture() {
let longPressRecogniser = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))
longPressRecogniser.minimumPressDuration = 0.25
mapView.addGestureRecognizer(longPressRecogniser)
}
func handleLongPress(gestureRecognizer: UIGestureRecognizer) {
guard gestureRecognizer.state == .Began else {
return
}
let touchPoint = gestureRecognizer.locationInView(self.mapView)
let touchMapCoordinate = mapView.convertPoint(touchPoint, toCoordinateFromView: mapView)
let annotation = MKPointAnnotation()
// The annotation must have a title in order for it to be selectable.
// Without a title the annotation is not selectable, and therefore not draggable.
annotation.title = "Point \(annotations.count)"
annotation.coordinate = touchMapCoordinate
mapView.addAnnotation(annotation)
// Add the new annotation to the list.
annotations.append(annotation)
// Redraw the overlay.
updateOverlay()
}
#IBAction func drawAction(sender: AnyObject) {
updateOverlay()
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
var view = mapView.dequeueReusableAnnotationViewWithIdentifier("pin")
if let view = view {
view.annotation = annotation
}
else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
// Allow the pin to be repositioned.
view?.draggable = true
}
return view
}
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
// The map view retains a reference to the same annotations in the array.
// The annotation in the array is automatically updated when the pin is moved.
updateOverlay()
}
func updateOverlay() {
// Remove existing overlay.
if let polygon = self.polygon {
mapView.removeOverlay(polygon)
}
self.polygon = nil
if annotations.count < 3 {
print("Not enough coordinates")
return
}
// Create coordinates for new overlay.
let coordinates = annotations.map({ $0.coordinate })
// Sort the coordinates to create a path surrounding the points.
// Remove this if you only want to draw lines between the points.
var hull = sortConvex(coordinates)
let polygon = MKPolygon(coordinates: &hull, count: hull.count)
mapView.addOverlay(polygon)
self.polygon = polygon
}
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKPolygon {
let polygonView = MKPolygonRenderer(overlay: overlay)
polygonView.strokeColor = UIColor.blackColor()
polygonView.lineWidth = 0.5
return polygonView
}
return MKPolylineRenderer()
}
}
Here is the convex hull sorting algorithm (adapted from this Gist on GitHub).
func sortConvex(input: [CLLocationCoordinate2D]) -> [CLLocationCoordinate2D] {
// X = longitude
// Y = latitude
// 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
// Returns a positive value, if OAB makes a counter-clockwise turn,
// negative for clockwise turn, and zero if the points are collinear.
func cross(P: CLLocationCoordinate2D, _ A: CLLocationCoordinate2D, _ B: CLLocationCoordinate2D) -> Double {
let part1 = (A.longitude - P.longitude) * (B.latitude - P.latitude)
let part2 = (A.latitude - P.latitude) * (B.longitude - P.longitude)
return part1 - part2;
}
// Sort points lexicographically
let points = input.sort() {
$0.longitude == $1.longitude ? $0.latitude < $1.latitude : $0.longitude < $1.longitude
}
// Build the lower hull
var lower: [CLLocationCoordinate2D] = []
for p in points {
while lower.count >= 2 && cross(lower[lower.count-2], lower[lower.count-1], p) <= 0 {
lower.removeLast()
}
lower.append(p)
}
// Build upper hull
var upper: [CLLocationCoordinate2D] = []
for p in points.reverse() {
while upper.count >= 2 && cross(upper[upper.count-2], upper[upper.count-1], p) <= 0 {
upper.removeLast()
}
upper.append(p)
}
// Last point of upper list is omitted because it is repeated at the
// beginning of the lower list.
upper.removeLast()
// Concatenation of the lower and upper hulls gives the convex hull.
return (upper + lower)
}
This is how it would look with the convex hull sorting (path drawn around points):
This is how it looks without sorting (path drawn from point to point in sequence):

Thank #Luke.
I update Luke's sortConvex function,
so make it compiled successfully by Swift 5.
func sortConvex(input: [CLLocationCoordinate2D]) -> [CLLocationCoordinate2D] {
// X = longitude
// Y = latitude
// 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
// Returns a positive value, if OAB makes a counter-clockwise turn,
// negative for clockwise turn, and zero if the points are collinear.
func cross(P: CLLocationCoordinate2D, _ A: CLLocationCoordinate2D, _ B: CLLocationCoordinate2D) -> Double {
let part1 = (A.longitude - P.longitude) * (B.latitude - P.latitude)
let part2 = (A.latitude - P.latitude) * (B.longitude - P.longitude)
return part1 - part2;
}
// Sort points lexicographically
let points: [CLLocationCoordinate2D] = input.sorted { a, b in
a.longitude < b.longitude || a.longitude == b.longitude && a.longitude < b.longitude
}
// Build the lower hull
var lower: [CLLocationCoordinate2D] = []
for p in points {
while lower.count >= 2 {
let a = lower[lower.count - 2]
let b = lower[lower.count - 1]
if cross(P: p, a, b) > 0 { break }
lower.removeLast()
}
lower.append(p)
}
// Build upper hull
var upper: [CLLocationCoordinate2D] = []
for p in points.lazy.reversed() {
while upper.count >= 2 {
let a = upper[upper.count - 2]
let b = upper[upper.count - 1]
if cross(P: p, a, b) > 0 { break }
upper.removeLast()
}
upper.append(p)
}
// Last point of upper list is omitted because it is repeated at the
// beginning of the lower list.
upper.removeLast()
// Concatenation of the lower and upper hulls gives the convex hull.
return (upper + lower)
}

Related

OpenStreetMap tile polygon click detect silently down

I'm working on the OpenStreetMap for display polygons. Here I'm using MKMapView and set tile of OpenStreetMap.
OSM tile is http://tile.openstreetmap.org/\(path.z)/\(path.x)/\(path.y).png
It's working fine and draw the polygon on the accurate position. (please find the attachment below) I have added the UITapGestureRecognizer on the polygon which is drawn on the map for getting click event. The only issue is when I tap on the polygon it detects silently down. please check the video here.
Code of tile:
func setupTileRenderer() {
let overlay = AdventureMapOverlay()
overlay.canReplaceMapContent = true
mapView.addOverlay(overlay, level: MKOverlayLevel.aboveLabels)
tileRenderer = MKTileOverlayRenderer(tileOverlay: overlay)
overlay.minimumZ = 13
overlay.maximumZ = 16
}
extension MapViewVC : MKMapViewDelegate {
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let overlay = overlay as? myPolygon {
let renderer = MKPolygonRenderer(polygon: overlay)
renderer.fillColor = overlay.color //UIColor.black.withAlphaComponent(0.5)
renderer.strokeColor = UIColor.orange
renderer.lineWidth = 2
return renderer
}
return tileRenderer
}
func removeMapOverlay(){
self.mapView.removeOverlays(self.arrPolygons.compactMap({$0.mkOverlay}))
}
}
Tap event:
#objc func mapTapped(_ gesture: UITapGestureRecognizer){
let point = gesture.location(in: self.mapView)
let coordinate = self.mapView.convert(point, toCoordinateFrom: nil)
//print("Tap \(coordinate)")
for overlay in self.mapView.overlays {
if let polygon = overlay as? MKPolygon {
guard (self.mapView.renderer(for: polygon) as? MKPolygonRenderer) != nil else { continue }
if polygon.contain(coor: coordinate){
print("Tap was inside this polygon \(coordinate)")
break
}
continue
}
}
}
extension MKPolygon {
func contain(coor: CLLocationCoordinate2D) -> Bool {
let polygonRenderer = MKPolygonRenderer(polygon: self)
let currentMapPoint: MKMapPoint = MKMapPoint(coor)
let polygonViewPoint: CGPoint = polygonRenderer.point(for: currentMapPoint)
return polygonRenderer.path.contains(polygonViewPoint)
}
}
P.S.: I'm also finding way to render OpenStreetMap offline line same as MKMapView.
Any help will be appreciated. Thank you. :)

keep zoom when using mkmapcamera issue

I'm newbie on IOS develope app with Swift 3 and Xcode 8.
In my app I use regionDidChangeAnimated delegate method to keep zoomLevel in meters by mapView.region.span.latitudeDelta * 111.000.
When new location data is available I use MKMapCamera to show userlocation with fromDistance parameter = mapView.region.span.latitudeDelta * 111.000 previously calculated and saved in instance variable.
The issue is when I pinch in and pinch out zoom level does not work properly
Below post a bit code:
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
mapChangedFromUserInteraction = mapViewRegionDidChangeFromUserInteraction()
if (mapChangedFromUserInteraction) {
self.latitudineDelta = Float(mapView.region.span.latitudeDelta)
}
}
func mapViewRegionDidChangeFromUserInteraction() -> Bool {
let view = self.mapView.subviews[0]
if let gestureRecognizers = view.gestureRecognizers {
for recognizer in gestureRecognizers {
if(recognizer.state == UIGestureRecognizerState.ended)
{
return true
}
}
}
return false
}
fileprivate func centerMapOnLocation()
{
let coordinate = CLLocationCoordinate2D(latitude: self.latitude!,longitude: self.longitude!)
if (mapChangedFromUserInteraction == false)
{
let distance: CLLocationDistance = CLLocationDistance(Int(self.latitudineDelta * 111000))
self.camera = MKMapCamera(lookingAtCenter: coordinate,
fromDistance: distance,
pitch: pitch,
heading: self.heading)
self.mapView.setCamera(self.camera!, animated: isAnimated)
}
}

How to get a MKAnnotationView that's draggable without any delay?

The below code works and gives me a draggable annotation view. However, I've noticed that the annotation view is not draggable from the very beginning on, but rather only after the finger has rested for a short moment on the annotation view. When directly going into a drag movement the dragging doesn't affect the annotation view but instead pans the map. It certainly doesn't feel like drag'n'drop. Both on the device and in the simulator.
ViewController (Delegate of the MapView)
override func viewDidLoad() {
/// ...
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(addPin))
mapView.addGestureRecognizer(gestureRecognizer)
}
func addPin(gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer.state != UIGestureRecognizerState.Began {
return
}
for annotation in mapView.annotations {
mapView.removeAnnotation(annotation)
}
let touchLocationInView = gestureRecognizer.locationInView(mapView)
let coordinate = mapView.convertPoint(touchLocationInView, toCoordinateFromView: mapView)
let annotation = DragAnnotation(coordinate: coordinate, title: "draggable", subtitle: "")
mapView.addAnnotation(annotation)
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation.isKindOfClass(DragAnnotation) {
let reuseIdentifier = "DragAnnotationIdentifier"
var annotationView: MKAnnotationView!
if let dequeued = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseIdentifier) {
annotationView = dequeued
} else {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
annotationView.annotation = annotation
annotationView.image = UIImage(named: "bluedisk2")
annotationView.canShowCallout = false
annotationView.draggable = true
return annotationView
}
return nil
}
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
switch (newState) {
case .Starting:
view.dragState = .Dragging
case .Ending, .Canceling:
view.dragState = .None
default: break
}
}
DragAnnotation
import UIKit
import MapKit
class DragAnnotation : NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D {
didSet {
print(coordinate)
}
}
var title: String?
var subtitle: String?
init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
}
}
I don't think you can change the draggable delay, but you could disable it and add your own drag gesture that has no delay (or a shorter delay):
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation { return nil }
var view = mapView.dequeueReusableAnnotationViewWithIdentifier("Foo")
if view == nil {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "Foo")
view?.draggable = false
let drag = UILongPressGestureRecognizer(target: self, action: #selector(handleDrag(_:)))
drag.minimumPressDuration = 0 // set this to whatever you want
drag.allowableMovement = .max
view?.addGestureRecognizer(drag)
} else {
view?.annotation = annotation
}
return view
}
private var startLocation = CGPointZero
func handleDrag(gesture: UILongPressGestureRecognizer) {
let location = gesture.locationInView(mapView)
if gesture.state == .Began {
startLocation = location
} else if gesture.state == .Changed {
gesture.view?.transform = CGAffineTransformMakeTranslation(location.x - startLocation.x, location.y - startLocation.y)
} else if gesture.state == .Ended || gesture.state == .Cancelled {
let annotationView = gesture.view as! MKAnnotationView
let annotation = annotationView.annotation as! DragAnnotation
let translate = CGPoint(x: location.x - startLocation.x, y: location.y - startLocation.y)
let originalLocation = mapView.convertCoordinate(annotation.coordinate, toPointToView: mapView)
let updatedLocation = CGPoint(x: originalLocation.x + translate.x, y: originalLocation.y + translate.y)
annotationView.transform = CGAffineTransformIdentity
annotation.coordinate = mapView.convertPoint(updatedLocation, toCoordinateFromView: mapView)
}
}
By the way, if you're going to change the coordinate of an annotation, you will want to add dynamic keyword to your custom class coordinate declaration so that the appropriate KVO notifications will be issued.
class DragAnnotation: NSObject, MKAnnotation {
dynamic var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
init(coordinate: CLLocationCoordinate2D, title: String? = nil, subtitle: String? = nil) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
super.init()
}
}
Note, in this example, I set the delay to zero. That means that if you tap on the annotation (the typical UI for "show the callout"), this may prevent that from working correctly. It will treat this as a very short drag. This is why the standard UI requires a delay in the long press before dragging.
So, for this reason, I personally would hesitate to override this behavior shown above because it will be different then standard map behavior that the end user is familiar with. If your map behaves differently than any other maps on the user's device, this could be a source of frustration.

How to detect the mapView was moved in Swift and update zoom

I'm trying to set a minimum zoom level on my map in Swift 2. I can't find any documentation on how to restrict a map from being zoomed too far in. What I've decided to try is to monitor for map movement (such as drag or zoom) and then set MKZoomScaleback to a minimum.
Most of the answers I've found for regionDidChangeAnimated are in Objective C, which I don't know and I'm having trouble converting them to Swift.
I tried implementing #hEADcRASH's answer: https://stackoverflow.com/a/30924768/4106552, but it doesn't trigger and print anything to the console when the map is moved in the simulator.
Can anyone tell me what I'm doing wrong? I'm new to Swift, so it could be a small error. Also, let me know if there is a lightweight way to solve for restricting the zoom level on a map. I'm worried that the monitor for movement will slow down the map animation a bit. Thanks for the help.
Here is my view controller.
import UIKit
import Parse
import MapKit
class SearchRadiusViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var map: MKMapView!
#IBOutlet weak var menuBtn: UIBarButtonItem!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
//menu button control
if self.revealViewController() != nil {
menuBtn.target = self.revealViewController()
menuBtn.action = "revealToggle:"
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
//user location
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//set map
let location:CLLocationCoordinate2D = manager.location!.coordinate
let latitude = location.latitude
let longitude = location.longitude
let latDelta:CLLocationDegrees = 0.1
let longDelta:CLLocationDegrees = 0.1
let span:MKCoordinateSpan = MKCoordinateSpanMake(latDelta, longDelta)
let maplocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(maplocation, span)
map.setRegion(region, animated: true)
//stop updating location, only need user location once to position map.
manager.stopUpdatingLocation()
}
//Attempt to monitor for map movement based on hEADcRASH's answer.
private var mapChangedFromUserInteraction = false
private func mapViewRegionDidChangeFromUserInteraction() -> Bool {
let view = self.map.subviews[0]
// Look through gesture recognizers to determine whether this region change is from user interaction
if let gestureRecognizers = view.gestureRecognizers {
for recognizer in gestureRecognizers {
if( recognizer.state == UIGestureRecognizerState.Began || recognizer.state == UIGestureRecognizerState.Ended ) {
return true
}
}
}
return false
}
func mapView(mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
print("yes")
mapChangedFromUserInteraction = mapViewRegionDidChangeFromUserInteraction()
if (mapChangedFromUserInteraction) {
// user changed map region
print("user changed map in WILL")
}
}
func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
print("yes ddd")
if (mapChangedFromUserInteraction) {
// user changed map region
print("user changed map in Did")
}
}
}
After reviewing and combining a number of other questions/answers and the help of #lorenzoliveto I've got it working in Swift. Please leave a comment if there a better/more lightweight way to achieve the same thing.
I added self.map.delegate = self to the viewDidLoad function.
Below is the code for how I'm monitoring for map movement and then once a user has zoomed in "too far" and the width of the map goes below 2 miles I then zoom out the map using mapView.setRegion.
private var mapChangedFromUserInteraction = false
private func mapViewRegionDidChangeFromUserInteraction() -> Bool {
let view = self.map.subviews[0]
// Look through gesture recognizers to determine whether this region change is from user interaction
if let gestureRecognizers = view.gestureRecognizers {
for recognizer in gestureRecognizers {
if( recognizer.state == UIGestureRecognizerState.Began || recognizer.state == UIGestureRecognizerState.Ended ) {
return true
}
}
}
return false
}
func mapView(mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
mapChangedFromUserInteraction = mapViewRegionDidChangeFromUserInteraction()
if (mapChangedFromUserInteraction) {
// user will change map region
print("user WILL change map.")
// calculate the width of the map in miles.
let mRect: MKMapRect = mapView.visibleMapRect
let eastMapPoint = MKMapPointMake(MKMapRectGetMinX(mRect), MKMapRectGetMidY(mRect))
let westMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), MKMapRectGetMidY(mRect))
let currentDistWideInMeters = MKMetersBetweenMapPoints(eastMapPoint, westMapPoint)
let milesWide = currentDistWideInMeters / 1609.34 // number of meters in a mile
print(milesWide)
print("^miles wide")
// check if user zoomed in too far and zoom them out.
if milesWide < 2.0 {
var region:MKCoordinateRegion = mapView.region
var span:MKCoordinateSpan = mapView.region.span
span.latitudeDelta = 0.04
span.longitudeDelta = 0.04
region.span = span;
mapView.setRegion(region, animated: true)
print("map zoomed back out")
}
}
}
func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if (mapChangedFromUserInteraction) {
// user changed map region
print("user CHANGED map.")
print(mapView.region.span.latitudeDelta)
print(mapView.region.span.longitudeDelta)
// calculate the width of the map in miles.
let mRect: MKMapRect = mapView.visibleMapRect
let eastMapPoint = MKMapPointMake(MKMapRectGetMinX(mRect), MKMapRectGetMidY(mRect))
let westMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), MKMapRectGetMidY(mRect))
let currentDistWideInMeters = MKMetersBetweenMapPoints(eastMapPoint, westMapPoint)
let milesWide = currentDistWideInMeters / 1609.34 // number of meters in a mile
print(milesWide)
print("^miles wide")
// check if user zoomed in too far and zoom them out.
if milesWide < 2.0 {
var region:MKCoordinateRegion = mapView.region
var span:MKCoordinateSpan = mapView.region.span
span.latitudeDelta = 0.04
span.longitudeDelta = 0.04
region.span = span;
mapView.setRegion(region, animated: true)
print("map zoomed back out")
}
}
UPDATE: 3/7, I discovered an interesting bug in the implementation above. On the simulator it works fine when clicking to zoom, but when you use the pinch to zoom (option + click) the simulator stops allowing you to drag the map around after it animates the zoom back out. This also happened on the beta version on my iphone. I added dispatch_async around the blocks that animate that map back to their position and it appears to be working on the simulator. It no longer appears frozen after it animates and I can continue to drag around the map and try to zoom in.
dispatch_async(dispatch_get_main_queue(), {
var region:MKCoordinateRegion = mapView.region
var span:MKCoordinateSpan = mapView.region.span
span.latitudeDelta = 0.04
span.longitudeDelta = 0.04
region.span = span;
mapView.setRegion(region, animated: true)
print("map zoomed back out")
})
The solution that works for me is one where I set the zoom range. This approach may not have been available at the time the question was asked.
The code fragment below is what I use. I'm not entirely sure what the distance units are, but I believe they are meters. Figuring out what range works in a given instance may be a matter of trial and error.
let mapView = MKMapView(frame: .zero)
let zoomRange = MKMapView.CameraZoomRange(
minCenterCoordinateDistance: 120000,
maxCenterCoordinateDistance: 1600000
)
mapView.cameraZoomRange = zoomRange

No Labels being displayed in MapKit

Could someone let me know why my labels are not being displayed. I am running a loop thru an array with coordinates in it. Its displaying my 3 pins with 1 being green, two blue which is what i want but my labels are not being displayed, any ideas?
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var maps: MKMapView!
var locationManager = CLLocationManager()
var flag = true
override func viewDidLoad() {
super.viewDidLoad()
let CoordinatesArray = ["blah,-blah, 11:45","blah, -blah,00:00", "blah,-blah, 12:45"];
self.maps.delegate = self
sendPoints(CoordinatesArray);
}
func sendPoints(array:[String]){
let latDelta:CLLocationDegrees = 0.015 //difference of lats from one side of screen to another
let longDelta:CLLocationDegrees = 0.015 //difference of lats from one side of screen to another
let span:MKCoordinateSpan = MKCoordinateSpanMake(latDelta, longDelta)
for (var i=0;i<array.count;i++){
var separateComma = array[i].componentsSeparatedByString(",")
var location:CLLocationCoordinate2D
if(flag){
location = CLLocationCoordinate2DMake(separateComma[0].doubleValue,separateComma[1].doubleValue)
let length:CLLocationDistance = 200
let cir:MKCircle = MKCircle(centerCoordinate: location, radius: length)
maps.addOverlay(cir)
}else{
location = CLLocationCoordinate2DMake(separateComma[0].doubleValue,separateComma[1].doubleValue)
}
let point = MKPointAnnotation()
point.title = "Home"
point.subtitle = "time for home"
point.coordinate = location
maps.addAnnotation(point)
maps.selectAnnotation(point, animated: true)
maps.setRegion(MKCoordinateRegionMake(point.coordinate, MKCoordinateSpanMake(latDelta,longDelta)), animated: true)
}
}
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
let overlayRenderer : MKCircleRenderer = MKCircleRenderer(overlay: overlay);
overlayRenderer.lineWidth = 150
overlayRenderer.fillColor = UIColor.blueColor()
overlayRenderer.alpha = 0.15
return overlayRenderer
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if (annotation.isKindOfClass(MKUserLocation)){
return nil
}
var myPin = mapView.dequeueReusableAnnotationViewWithIdentifier("MyIdentifier") as? MKPinAnnotationView
if myPin != nil {
return myPin
}
myPin = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "MyIdentifier")
if(flag){
myPin?.pinTintColor = UIColor.greenColor()
}else{
myPin?.pinTintColor = UIColor.blueColor()
}
flag = false;
return myPin
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension String {
var doubleValue: Double {
return (self as NSString).doubleValue
}
}
You have to set canShowCallout property.
myPin = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "MyIdentifier")
myPin?.canShowCallout = true
In my case I was delegating the map before being called.
So... that'd be the mistake I was facing.
self.m_map = MKMapView()
self.m_map?.delegate = self
Hope it helps somebody :-)

Resources