Issues with map view camera heading - ios

I am having issues with the order/way that I'm setting up my UIMapView. This is what I would like to happen:
View appears - Map rotates to specified heading
Reset button tapped - If user has moved the map, it will reset to the default heading and zoom
At the moment the map is rotated to the heading when the map appears, but the reset button does nothing. I suspect this is down to the order I am doing things because if I flip two lines of code around, it works, but it doesn't rotate to the correct heading when the map appears.
Here is my code:
#IBAction func rotateToDefault(sender: AnyObject) {
mapView.setRegion(zoomRegion, animated: true)
mapView.camera.heading = parkPassed.orientation!
}
override func viewWillAppear(animated: Bool) {
setUpMapView()
}
override func viewDidAppear(animated: Bool) {
mapView.setRegion(zoomRegion, animated: true)
mapView.camera.heading = parkPassed.orientation!
}
func setUpMapView() {
rideArray = ((DataManager.sharedInstance.rideArray) as NSArray) as! [Ride]
zoomRegion = MKCoordinateRegionMakeWithDistance(CLLocationCoordinate2D(latitude: parkPassed.latitude!, longitude: parkPassed.longitude!), 1000, 1000)
mapView.setRegion(zoomRegion, animated: true)
mapView.delegate = self
for ride in rideArray {
var subtitle = ""
if locationManager.location == nil {
subtitle = "Distance unavailable"
} else {
let userLocation = CLLocation(latitude: locationManager.location.coordinate.latitude, longitude: locationManager.location.coordinate.longitude)
let annotationLocation = CLLocation(latitude: ride.latitude!, longitude: ride.longitude!)
var distance = Int(CLLocationDistance(annotationLocation.distanceFromLocation(userLocation)))
if distance > 1000 {
distance = distance / 1000
subtitle = "\(distance) kilometers"
} else {
subtitle = "\(distance) meters"
}
}
let annotation = RideAnnotation(coordinate: CLLocationCoordinate2DMake(ride.latitude!, ride.longitude!), title: ride.name!, subtitle: subtitle)
self.qTree.insertObject(annotation)
annotationsAdded.insertObject(annotation, atIndex: 0)
println(qTree.count)
}
}
Anyone have any suggestions?

I had a similar problem.
It appears region and camera are two mutual exclusive concepts to determine how you see which part of the map.
If you use region, you have coordinate and span to determine what you see (you already did this in your code)
If you use camera, you have coordinate, distance, pitch and heading to determine how you see the map.
use mapView.setCamera(...) to smoothly change what you see, including the heading.
To define your camera view you do something like
let camera = MKMapCamera(lookingAtCenterCoordinate: userLocation, fromDistance: 1000, pitch: 0, heading: heading)
self.mapView.setCamera(camera, animated: false)
From Apples Documentation:
Assigning a new camera to this property updates the map immediately and without animating the change. If you want to animate changes in camera position, use the setCamera:animated: method instead.

Related

Swift: Map returns 4 values as the distance between 2 points, why?

I am calculatig the distance between 2 points. For the first point, I am assigning the latitude and the longtiude. The second point coordinates wherever the user clicks on the map. Every thing works perfectly, except the answer being printed 4 times.
I tried to return the first index(it turned out the first index is 4
values).
I tried to add these values to a set to delete the repeated ones (it did not work).
I tried to make a loop, and break after the first time (Still return 4 values).
Here where I am creating the annotation for the user click (inside viewdidAppear):
,
let uilpgr = UILongPressGestureRecognizer(target: self, action: #selector(MapViewController.longpress(gestureRecognizer:)))
uilpgr.minimumPressDuration = 2
map.addGestureRecognizer(uilpgr)
And this is the function that calculates the distance (After the function viewdidAppear):
#objc func longpress(gestureRecognizer: UIGestureRecognizer){
let touchPoint = gestureRecognizer.location(in: self.map)
let coordinates = map.convert(touchPoint, toCoordinateFrom: self.map)
let annotation = MKPointAnnotation()
annotation.coordinate = coordinates
annotation.title = "Destination"
map.addAnnotation(annotation)
let source = CLLocation(latitude: 5, longitude: 5)
let distination = CLLocation(latitude: coordinates.latitude, longitude: coordinates.longitude)
let distanceInMeters = source.distance(from: distination)
let distanceInMiles = String(Int(distanceInMeters/1.6))
myArrayDataStructure.myArray.append(distanceInMiles)
UserDefaults.standard.set(myArrayDataStructure.myArray, forKey: "items")
}
Your problem is how you handle the gesture. A "long press" gesture (like others) has several states. A gesture action is called when the gesture begins, when it ends, when it is cancelled, and in the case of a "long press", as the gesture is updated.
You want to perform your distance calculations only when the gesture's state is "ended".
#objc func longpress(gestureRecognizer: UIGestureRecognizer){
if gestureRecognizer.state = .ended {
// your code here
}
}

How to animate an coordinate change of an MKPointAnnotation correctly

I want to animate an coordinate change of an MKPointAnnotation on a map view like this:
let destinationLocation = CLLocationCoordinate2D(latitude: 51.873983, longitude: 12.627966)
UIView.animate(withDuration: 20, animations: {
self.myAnnotation.coordinate = destinationLocation
}, completion: nil)
I added the pin on the map like this:
let startPosition = CLLocationCoordinate2D(latitude: 53.014542, longitude: 13.996593)
myAnnotation.coordinate = startPosition
mapView.addAnnotation(myAnnotation)
myAnnotation is a global variable declared like this in my ViewController:
private let myAnnotation = MKPointAnnotation()
But when I zoom the map during the animation (method regionDidChangeAnimated from the MKMapViewDelegate is called) the position of the pin is moving to an wrong location on the map and not to the correct map coordinate during zooming.
I upload an Screencast here which describes the problem very well.
https://wetransfer.com/downloads/46a0ea2bdc0703ac392581532d3713f920170630095741/7f6499e7a38737a1f04740cd933ddaee20170630095741/dd2b2d
I don't know another possibility to provide a short video. The size is only 8 MB. That is the best way to see the issue.
Whenever the user interacts with the map (zoom, scroll) you could/should stop the animation
if let annotationView = self.mapView.view(for: self.myAnnotation) {
annotationView.layer.removeAllAnimations()
}
Furthermore you can implement the completion handler of your animation to act correctly on a canceled/successful animation:
UIView.animate(withDuration: 20, animations: {
self.myAnnotation.coordinate = destinationLocation
}, completion: { success in
if success {
// handle a successfully ended animation
} else {
// handle a canceled animation, i.e move to destination immediately
self.myAnnotation.coordinate = destinationLocation
}
})

Moving Google Maps Camera to a Location

I'm working with a Google Maps View and I want to add a button to the map that when tapped, will move the camera to a specific location. I currently have a button outlet and an action connected to the button.
#IBAction func locationTapped(_ sender: Any) {
print("tapped")
let location = GMSCameraPosition.camera(withLatitude: place.latitude, longitude: place.longitude, zoom: 17.0)
mapView.camera = location
}
place exists but for some reason, the camera will not budge. I've tried different versions of code and looked at the Google Maps documentation but none of the options are producing results. Can anyone tell me what I'm doing wrong?
The GMSMapView class has the following function:
animate(to: GMSCameraPosition)
So in your code sample, instead of doing this:
mapView.camera = location
Try doing this:
mapView.animate(to: location)
Hope this helps!
in Swift3 and Swift4
for moving marker to current position use this:
func myLocationBtnAction(_ sender: UIButton) {
mapView.moveCamera(GMSCameraUpdate.setTarget(CLLocationCoordinate2D(latitude: (mapView.myLocation?.coordinate.latitude)!, longitude: (mapView.myLocation?.coordinate.longitude)!), zoom: 16))
and for a specific location use this:
let camera = GMSCameraPosition.camera(withLatitude: lat, longitude: lng, zoom: 16)
mapView?.camera = camera
mapView?.animate(to: camera)
and don't forget to extend GMSAutocompleteViewControllerDelegate for current location
Swift 2.3
This code is used for my purpose. In which marker tap event used, which moves camera position of map. Hope you find your solution.
func mapView(mapView: GMSMapView, didTapMarker marker: GMSMarker) -> Bool {
mapView.selectedMarker = marker
var point = mapView.projection.pointForCoordinate(marker.position)
let camera = mapView.projection.coordinateForPoint(point)
let position = GMSCameraUpdate.setTarget(camera)
mapView.animateWithCameraUpdate(position)
return true
}
For Objective-c the method is:
[mapView moveCamera:[GMSCameraUpdate setTarget:<CLLocationCoordinate2DMake>]];
Maybe this is to late but i resolve that problem with adding this:
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500), execute: {
let camera = GMSCameraPosition.camera(withLatitude: lat!, longitude: lon!, zoom: 17.0)
self.mMap.animate(to: camera)
})
You have to wait until map load delegate
func moveMapCamera(at cordinate: CLLocationCoordinate2D, animated: Bool = false) {
let camera = MKMapCamera()
camera.centerCoordinate = cordinate
camera.pitch = 0
camera.altitude = 9000
mapV.setCamera(camera, animated: animated)
}

unable to scroll MKMapView.

I'm trying to do an iOS app using Xcode 6.3 and swift. I use MKMapView for tracking user position. The problem is that if I scroll the map I immediately return to the user position. This is my code:
override func viewDidLoad() {
super.viewDidLoad()
manager = CLLocationManager()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()
theMap.delegate = self
theMap.mapType = MKMapType.Standard
theMap.zoomEnabled = true
theMap.addGestureRecognizer(longPress)
theMap.scrollEnabled = true
}
and
func locationManager(manager:CLLocationManager, didUpdateLocations locations:[AnyObject]) {
let spanX = 0.007
let spanY = 0.007
var newRegion = MKCoordinateRegion(center: theMap.userLocation.coordinate, span: MKCoordinateSpanMake(spanX, spanY))
theMap.setRegion(newRegion, animated: false)
theMap.scrollEnabled = true
}
If I scroll the map, after 1 sec I return to the user position. Should I change setRegion method position?
You need to detect when you scroll the map, possibly by implementing the mapView(_:regionWillChangeAnimated:) method defined in MKMapViewDelegate. In this method you need to set the userTrackingMode property of your map view to .None. Your implementation will be called when the user pans or zooms your theMap variable. So you should strive to keep the implementation as lightweight as possible since it could be called many times for a single pan or zoom gesture.
func mapView(mapView: MKMapView!, regionWillChangeAnimated animated: Bool) {
if you want to stop tracking the user {
mapView.userTrackingMode = .None
}
}
When you want to start following the user's location again, change that property back to either .Follow or .FollowWithHeading:
enum MKUserTrackingMode : Int {
case None // the user's location is not followed
case Follow // the map follows the user's location
case FollowWithHeading // the map follows the user's location and heading
}

setUserTrackingMode to MKUserTrackingModeFollow without changing zoom level

At Apple Documentation of setUserTrackingMode:animated:
it is stated that:
Setting the tracking mode to MKUserTrackingModeFollow or MKUserTrackingModeFollowWithHeading causes the map view to center the map on that location and begin tracking the user’s location. If the map is zoomed out, the map view automatically zooms in on the user’s location, effectively changing the current visible region.
My question, is there a way we can retain current zoom level on map, while setting the user tracking mode?
The way i was able to do this was to call a MKCoordinateRegionMakeWithDistance call that uses the users location and center. I used 5000 for my values. This is what my test code looks like. `import UIKit
import CoreLocation
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate{
#IBOutlet weak var mapView: MKMapView!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
if (CLLocationManager.locationServicesEnabled()){
mapView.showsUserLocation = true
mapView.mapType = MKMapType.satellite
mapView.setUserTrackingMode(MKUserTrackingMode.followWithHeading, animated: true)
//locationManager = CLLocationManager()
//locationManager.delegate = self
//locationManager.desiredAccuracy = kCLLocationAccuracyBest
//locationManager.requestAlwaysAuthorization()
//locationManager.startUpdatingLocation()
}
}
/*func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
let location = locations.last as! CLLocation
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
//let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
let region = MKCoordinateRegionMakeWithDistance(center, 500, 500)
self.mapView.setRegion(region, animated: true)
}*/
#IBAction func centerButton(_ sender: UIButton, forEvent event: UIEvent) {
let location = MKUserLocation()
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let region = MKCoordinateRegionMakeWithDistance(center, 5000, 5000)
self.mapView.setRegion(region, animated: true)
mapView.setUserTrackingMode(MKUserTrackingMode.followWithHeading, animated: true)
}
}
`
dont read any of the commented out locationManager info. The important thing here is to remember that calling setUserTrackingMode does not affect the zoom level but just moves the center to the users location so if you set a zoom level using the region and distance method, and then call setUserTrackingMode it will assume that zoom. This allows me to always zoom out to a reasonable zoom level each time we recenter and follow the users current location.
As of iOS 13 you can set a minimum map zoom level, which prevents MKUserTrackingModeFollow getting any closer than the specified distance, when it is set:
let minZoom: CLLocationDistance = 10000 // desired visible radius from user in metres
let zoomRange = MKMapView.CameraZoomRange(minCenterCoordinateDistance: minZoom)
mapView.setCameraZoomRange(zoomRange, animated: true)
https://developer.apple.com/documentation/mapkit/mkmapview/camerazoomrange
No. One alternative is you could listen to user location updates yourself (either through Core Location or the MKMapViewDelegate methods and update the map center, but tracking mode can not guarantee to not change the zoom.

Resources