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
Related
I restrict pannable area in MapBox IOS by implementing mapview shouldchangefrom delegate according to the documentation https://docs.mapbox.com/ios/maps/examples/constraining-gestures/.
public func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera) -> Bool {
let currentCamera = mapView.camera
let newCameraCenter = newCamera.centerCoordinate
mapView.camera = newCamera
let newVisibleCoordinates = mapView.visibleCoordinateBounds
mapView.camera = currentCamera
let inside = MGLCoordinateInCoordinateBounds(newCameraCenter, self.allowedBounds!)
let intersects = MGLCoordinateInCoordinateBounds(newVisibleCoordinates.ne, self.allowedBounds!) && MGLCoordinateInCoordinateBounds(newVisibleCoordinates.sw, self.allowedBounds!)
return inside && intersects
}
It works, if the updates by a user interaction, but is does not prevent to update map by user location change. Show / track user location is a must in my case, but only in the enabled area.
mapView.showsUserLocation = showUserLocation
mapView.userTrackingMode = .followWithHeading
Is there any way to prevent updating map to a restricted area by user location?
So, finally I solved it by implementing a custom MGLLocationManager, and set it as mapview's locationmanager. When calling its updateLocation method I filter position by 'restricted' area, and if the pos is not within it, I cancel location update.
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
}
I have got mapView, and its delegate method
func mapView(mapView: MKMapView!, didUpdateUserLocation userLocation: MKUserLocation!) {
mapView.userTrackingMode = MKUserTrackingMode.None
var eye = CLLocationCoordinate2DMake(userLocation.coordinate.latitude, userLocation.coordinate.longitude)
var cam = MKMapCamera(lookingAtCenterCoordinate: eye, fromEyeCoordinate: eye, eyeAltitude: 10000)
UIView.animateWithDuration(1, animations: { () -> Void in
mapView.camera = cam
})
}
and it works some random way. it executes when view loads, and than when I am scrolling map to another country or city and zoom, it can(sometimes, now always) return me to my current location. It is curious cause my current location didnt changes and the delegate shouldnt execute.
I need do somehow this thing DONT HAPPENED. So i want to make my map zoom to current location only when location changed, in other way I need to have free map for scrolling.
You have to set you controller as CLLocationManagerDelegate, then declare a variable like:
var locationManager:CLLocationManager = CLLocationManager()
then in your viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.delegate = self
self.locationManager.stopUpdatingLocation()
It should prevent to call func mapView(mapView: MKMapView!, didUpdateUserLocation userLocation: MKUserLocation!).
Pay attention that you have to re-enbale it in case you need to update map center with your coordinate without manual scrolling to you actual position.
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.
I have a mapView in which I show a location (represented by custom pin) as seen in screenshot
How can I move the mapView so that the icon is visible completely?
To achieve this you first need to calculate the point on screen view where you want to scroll (In your case let assume its 20 pixel up to the annotation marker), Then you need to covert this point to Map location so that you can move map center to that location ;-). Following is the code written in Swift in MKMapView delegate methods where annotation gets tapped.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
let pinLocation = view.annotation.coordinate
let currentCoordinates = mapView.centerCoordinate // Temporary saved map current center position
// Temp set map center position to pin location
mapView.centerCoordinate = pinLocation
let viewCenter = self.view.center
let fakecenter = CGPoint(x: viewCenter.x, y: viewCenter.y - 20) // point just up to the center point
// convert calculetd point to map location co-ordinates
let coordinate: CLLocationCoordinate2D = mapView.convert(fakecenter, toCoordinateFrom: self.view)
// reset to previous potion so thet animation start from that
mapView.centerCoordinate = currentCoordinates
self.mapView.setCenter(coordinate, animated: true) // change the new center
}