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.
Related
i Have a car icon in google map and which have to be moved in regular time interval. the location coordinates will be fetched from server and i have managed to change the location of marker by doing the code below but didint got a smooth movement
let camera = GMSCameraPosition.camera(withLatitude: (((self.driverArrival.value(forKey: "latitude")) as AnyObject).doubleValue)!,
longitude: (((self.driverArrival.value(forKey: "longitude")) as AnyObject).doubleValue)!, zoom: 15)
let position = CLLocationCoordinate2DMake((((self.driverArrival.value(forKey: "latitude")) as AnyObject).doubleValue)!, (((self.driverArrival.value(forKey: "longitude")) as AnyObject).doubleValue)!)
driverMarker.position = position
driverMarker.map = self.mapView
here driverdetails contains all the required data. Just want to know i can use any animation functions to achieve this?
You can use animate(to: GMSCameraPosition) to update map position with animation an example will look like this :-
func updateMapLocation(lattitude:CLLocationDegrees,longitude:CLLocationDegrees){
let camera = GMSCameraPosition.camera(withLatitude: lattitude, longitude: longitude, zoom: 16)
mapView?.camera = camera
mapView?.animate(to: camera)
}
and call the method like this
updateMapLocation(lattitude:-33.8683,longitude:151.2086)
For more information
Edit
For marker position update you can use a single marker and update its position with this code
CATransaction.begin()
CATransaction.setAnimationDuration(2.0)
marker.position = coordindates // CLLocationCoordinate2D coordinate
CATransaction.commit()
Please Don't use GMSCameraPosition for move pin in the map
You can use mapView.animate(toLocation: YOUR_COORDINATES) method to smoothly move pin in the map
self.mapView.animate(toLocation: CLLocationCoordinate2D(latitude: YOUR_LATITUDE, longitude: YOUR_LONGITUDE))
self.marker.position = coordinate
self.marker.map = self.mapView
Hope this helps!
Try this ...
Add GMSMapViewDelegate to your class,
self.mapView.delegate = self //Call delegate
//MARK - MapView delegates
func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) {
self.marker.map = mapView;
self.marker.position = position.target //Your target position
self.mapView.selectedMarker = self.marker //Your marker
DispatchQueue.main.async {
self.marker.snippet = "Getting address... " //Your snippet title
}
}
func mapView(_ mapView:GMSMapView, idleAt position:GMSCameraPosition) {
//Get address with village names and street names
self.getAddressForLatLng(latitude: "\(position.target.latitude)", longitude: "\(position.target.longitude)", zoomLevel: position.zoom)
}
I have a Google Map view that centers on the users location each time the location manager receives an updated position. However I want the user to be able to freely pan/zoom around the map without the camera position jumping back to the users location every second or so.
I have set .isMyLocationEnabled to false as I have designed a custom icon (its a ship in my case) to represent the users location.
Furthermore I have created my own button to allow the user to pan back to the users last location (I am not using googles built in location button for the above reasons). This is not included in the example code but it does work fine.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
self.camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: 15)
self.mapViewHere.isMyLocationEnabled = false // don't want the standard blue dot or location button
self.mapViewHere.camera = self.camera
/// plot our custom GMSMarker here
let currentPosition = GMSMarker()
currentPosition.map = nil // clear the last location
currentPosition = GMSMarker(position: location.coordinate)
currentPosition.icon = myCustomMarker.png
currentPosition.rotation = (locationManager.location?.course)!
currentPosition.map = self.mapViewHere
} // end locations.first
}// end location manager
Because I am plotting the user location with a custom GMSMarker which is called in didUpdateLocations I didn't think that stopping the update function would be the answer as I would like this to continue even when the user zooms/pans.
So in summary; is it possible to show a map that initially centers on the users location (and updates as the device moves) but whenever the user pans/zooms around the mapview should not automatically return to the users location as soon as the didUpdateLocations fires? The User should be able to trigger a return to the initial state by pressing a custom button.
I am developing my app with Xcode and Swift 4.
Solved my question with much appreciated guidance from Perth iOS developers group;
First define a Boolean variable;
var auto: Bool = true
Listen for user actions and adjust the variable accordingly:
func mapView(_ mapView: GMSMapView, willMove gesture: Bool) {
if gesture {
self.auto = false
print("map view moved, self.auto set to \(self.auto)")
}
}
And use this variable inside locationManager didUpdateLocation to effectively ignore the map redrawing at users locations when didUpdateLocation fires.
if self.auto {
print("self.auto is true, back to auto-camera-tron")
self.camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: 15)
}
My custom location icon will keep drawing on didUpdateLocation as long as it is declared before if self.auto
Lastly, I can set my custom Location button to return to centering on user location by simply changing self.auto = true
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)
}
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.
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
}