Swift MapView Stuck Around User's Current Location - swift

Currently, my code drops a pin on the user's current location. There is one small problem when I try to move the map around, because the view will shift back and be centered around that current location pin. I want the user be able to navigate the map and move it around, and if the user switches view controllers (goes to another tab) and comes back, the map will be centered around the user location pin. I have been trying to modify this code to do this, but I have not had any luck where to start.
import UIKit
import MapKit
import CoreLocation
let newPin = MKPointAnnotation()
class MapVC: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var map: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// User's location
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
if #available(iOS 8.0, *) {
locationManager.requestAlwaysAuthorization()
} else {
// Fallback on earlier versions
}
locationManager.startUpdatingLocation()
// add gesture recognizer
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(MapVC.mapLongPress(_:))) // colon needs to pass through info
longPress.minimumPressDuration = 1.5 // in seconds
//add gesture recognition
map.addGestureRecognizer(longPress)
}
// func called when gesture recognizer detects a long press
func mapLongPress(_ recognizer: UIGestureRecognizer) {
print("A long press has been detected.")
let touchedAt = recognizer.location(in: self.map) // adds the location on the view it was pressed
let touchedAtCoordinate : CLLocationCoordinate2D = map.convert(touchedAt, toCoordinateFrom: self.map) // will get coordinates
let newPin = MKPointAnnotation()
newPin.coordinate = touchedAtCoordinate
map.addAnnotation(newPin)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
map.removeAnnotation(newPin)
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))
//set region on the map
map.setRegion(region, animated: true)
newPin.coordinate = location.coordinate
map.addAnnotation(newPin)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Your code has several problems:
You declare a variable newPin at global scope and in mapLongPress(...) you declare a new variable let newPin = ... locally so the global newPin isn't used.
In didUpdateLocations() you first remove the (global) newPin annotation (why??) and set it again at the end of the function. Because the global newPin was never set to anything useful this will never get the desired result.
Furthermore, in didUpdateLocations() you set the map's region and center point to the current location. This is done on every location update, giving weird results when trying to pan the map.
To set center and region when the view appears, try something like that:
class MapVC: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var map: MKMapView!
let locationManager = CLLocationManager()
// class variable for the current location
var lastLocation: CLLocation?
override func viewDidLoad() {
// ...
}
override func viewDidAppear(_ animated: Bool) {
if self.lastLocation != nil {
// set center and region to current location
let center = CLLocationCoordinate2D(latitude: self.lastLocation.coordinate.latitude, longitude: self.lastLocation.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
//set region on the map
map.setRegion(region, animated: true)
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.lastLocation = locations.last
}
}

Related

Create a clickable map preview using MKMapSnapshotter

I have a map that currently shows the user's current location with no pin to show them exactly where they are. I want to make the map a photo of where the user's set location is. I only know how to show where their device is and need them to be able to set their base of operations. I don't need specific addresses. I just need the city where they reside.
I then need the image to be able to be tapped on. When tapped, the image makes the MapKit full screen and interactive. They can then zoom around the map and see where other users' set their base of operations.
I am new to coding and can't figure out how to allow the user to set a permanent location even if they move around the country. I also don't know how to set up the mapsnapshot and on top of that expand when tapped to show a fully working map view.
I am only currently able to ask if I can activate location services and then show their map view where they are when it is loaded. Here is the code:
import UIKit
import CoreLocation
import MapKit
class HomeTableViewController: UITableViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
#IBOutlet weak var mapPreviewImageView: UIImageView!
#IBOutlet weak var mapView: MKMapView!
let manager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.desiredAccuracy = kCLLocationAccuracyBest // battery
manager.delegate = self
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
// Always adopt a light interface style.
overrideUserInterfaceStyle = .light
takeSnapShot()
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 7
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
manager.stopUpdatingLocation()
render(location)
}
}
func render (_ location: CLLocation) {
let coordinate = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
let region = MKCoordinateRegion(center: coordinate, span: span)
mapView.setRegion(region, animated: true)
}
func takeSnapShot() {
let location = CLLocation()
let mapSnapshotOptions = MKMapSnapshotter.Options()
let coordinate = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
let region = MKCoordinateRegion(center: coordinate, span: span)
mapSnapshotOptions.region = region
// Set the scale of the image. We'll just use the scale of the current device, which is 2x scale on Retina screens.
mapSnapshotOptions.scale = UIScreen.main.scale
// Show buildings and Points of Interest on the snapshot
mapSnapshotOptions.showsBuildings = true
mapSnapshotOptions.mapType = .satellite
let snapShotter = MKMapSnapshotter(options: mapSnapshotOptions)
snapShotter.start() { snapshot, error in
guard let snapshot = snapshot else {
return
}
self.mapPreviewImageView.image = snapshot.image
}
}
}
Thanks for your help in advance. I really need to make some progress on this app and I can't seem to find any tutorials or web results on how to do this.
Edit:
I have tried adding a function that turns my UIImage into the snapshot. I am able to return an image but it doesn't show my location and it is smaller than my UIImage. I had edited the code above to reflect the changes I made. I don't know what I am doing wrong.
In your example, you are creating a CLLocationManager, but not using it. You are using CLLocation(). That obviously has no (meaningful) coordinate associated with it. Make sure to supply a valid coordinate. For example, have didUpdateLocations call takeSnapshot:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
private weak var snapshotter: MKMapSnapshotter?
private lazy var manager: CLLocationManager = {
let manager = CLLocationManager()
manager.delegate = self
manager.distanceFilter = 20
return manager
}()
override func viewDidLoad() {
super.viewDidLoad()
if manager.authorizationStatus == .notDetermined {
manager.requestWhenInUseAuthorization()
}
manager.startUpdatingLocation()
}
func takeSnapshot(of location: CLLocation) {
snapshotter?.cancel() // cancel prior one, if any
let options = MKMapSnapshotter.Options()
options.camera = MKMapCamera(lookingAtCenter: location.coordinate, fromDistance: 1000, pitch: 0, heading: 0)
options.mapType = .satellite
options.size = imageView.bounds.size
let snapshotter = MKMapSnapshotter(options: options)
snapshotter.start() { snapshot, _ in
self.imageView.image = snapshot?.image
}
self.snapshotter = snapshotter
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last(where: { $0.horizontalAccuracy >= 0 } ) else { return }
takeSnapshot(of: location)
}
}
That yields:
Unrelated observations:
You are using MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1). I personally do not find spans in degrees to be terribly useful. I might advise using meters, e.g.
options.region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 1000, longitudinalMeters: 1000)
Or use a MKMapCamera:
options.camera = MKMapCamera(lookingAtCenter: location.coordinate, fromDistance: 1000, pitch: 0, heading: 0)
There is no point in using showsBuildings if you are using a map type of satellite. The docs say:
The mapType property must be set to MKMapType.standard for extruded buildings to be displayed.
I do not believe that you have to set the scale. The docs say:
This property is set to a default value that corresponds to the resolution of the current device’s display.
Besides, this property is now deprecated, anyway.
I would suggest, though, to set the size of the image.

How can I center the map on my own position?

On a xcode application, I display many pins from my database and my position but the map is not centered on my position
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Create coordinates from location lat/long
var poiCoodinates = CLLocationCoordinate2D()
tab = selectedEtablissement as! [EtablissementModel]
for item in 0..<tab.count{
poiCoodinates.latitude = tab[item].lonetablissement!
poiCoodinates.longitude = tab[item].lngetablissement!
// Zoom to region
let viewRegion: MKCoordinateRegion = MKCoordinateRegion.init(center: poiCoodinates, latitudinalMeters: 750, longitudinalMeters: 750)
self.mapB******.setRegion(viewRegion, animated: true)
// Plot pin
let pin: MKPointAnnotation = MKPointAnnotation()
pin.coordinate = poiCoodinates
self.mapB*******.addAnnotation(pin)
//add title to the pin
pin.title = tab[item].nometablissement
}
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.startUpdatingLocation()
mapBieres.showsUserLocation = true
self.locationManager.stopUpdatingLocation()
}
The pins are displayed on many cities and with this code, my position is displayed but the map is not centered on my position. I want all the pins on the map but the map centered on my position. Thanks
`import MapKit
import CoreLocation
class ViewController: UIViewController {
var locationManager = CLLocationManager()
#IBOutlet weak var mapView: MKMapView!
func setLocation() { // Seeting up current location
locationManager.delegate = self as CLLocationManagerDelegate
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
// Setting the current location to the ashton building, & the zoom of the map
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locationOfUser = locations[0]
let lat = locationOfUser.coordinate.latitude
let long = locationOfUser.coordinate.longitude
let latDelta: CLLocationDegrees = 0.003 // Distance from the map
let lonDelta: CLLocationDegrees = 0.003
let span = MKCoordinateSpan(latitudeDelta: latDelta, longitudeDelta: lonDelta)
let location = CLLocationCoordinate2D(latitude: lat, longitude: long)
let region = MKCoordinateRegion(center: location, span: span)
mapView.setRegion(region, animated: true)
}
}`
The following code should help you set up your current location, as well as the distance zoom of the map. (make sure current location is turned on in settings, if I remember correctly).
Hope this can help in anyway, had to look through my old Swift assignments:)

Fix MKPointAnnotation to centre of MapKit even while scrolling

i want to make MKPointAnnotation fixed to centre of the map even while scrolling, i try to make it but MKPointAnnotation is not move while scrolling
here is my code :
import UIKit
import MapKit
class HomeVC: UIViewController,MKMapViewDelegate,CLLocationManagerDelegate {
#IBOutlet var myMap: MKMapView!
private var locationManager = CLLocationManager();
private var userLocation: CLLocationCoordinate2D?;
// private var riderLocation: CLLocationCoordinate2D
override func viewDidLoad() {
super.viewDidLoad()
initializeLocationManager()}
// find location on the map
private func initializeLocationManager(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// if we hade the coordinate from the manager
if let location = locationManager.location?.coordinate {
userLocation = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
// the place the map will show (ZOOM LVL ON MAP)
let region = MKCoordinateRegion(center: userLocation!, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
myMap.setRegion(region, animated: true)
//To Remove annotation = Point on map befor add new one
myMap.removeAnnotations(myMap.annotations)
//Show My Point At Map
let annotation = MKPointAnnotation();
annotation.coordinate = myMap.centerCoordinate
myMap.addAnnotation(annotation)
}
}
}
Try the following code:
// Show My Point At Map
let annotation = MKPointAnnotation()
annotation.coordinate = myMap.region.center // instead of myMap.centerCoordinate
myMap.addAnnotation(annotation)
EDIT:
You need to call stopUpdatingLocation() in didUpdateLocations method.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// if we hade the coordinate from the manager
if let location = locationManager.location?.coordinate {
userLocation = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
// the place the map will show (ZOOM LVL ON MAP)
let region = MKCoordinateRegion(center: userLocation!, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
myMap.setRegion(region, animated: true)
locationManager.stopUpdatingLocation() // <- Stop updating
}
}
And you should use regionDidChangeAnimated method to make MKPointAnnotation fixed to centre of the map even while scrolling,
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
// To Remove annotation = Point on map befor add new one
myMap.removeAnnotations(myMap.annotations)
let annotation = MKPointAnnotation();
annotation.coordinate = myMap.region.center
myMap.addAnnotation(annotation)
}

Swift MkMapView Map is always centered around current location

Currently, my code drops a pin on the user's current location. There is one small problem when I try to move the map around, because the view will shift back and be centered around that current location pin. I want the user be able to navigate the map and move it around, and if the user switches view controllers (goes to another tab) and comes back, the map will be centered around the user location pin. I have been trying to modify this code to do this, but I have not had any luck where to start.
import UIKit
import MapKit
import CoreLocation
let newPin = MKPointAnnotation()
class MapVC: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var map: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// User's location
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
if #available(iOS 8.0, *) {
locationManager.requestAlwaysAuthorization()
} else {
// Fallback on earlier versions
}
locationManager.startUpdatingLocation()
// add gesture recognizer
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(MapVC.mapLongPress(_:))) // colon needs to pass through info
longPress.minimumPressDuration = 1.5 // in seconds
//add gesture recognition
map.addGestureRecognizer(longPress)
}
// func called when gesture recognizer detects a long press
func mapLongPress(_ recognizer: UIGestureRecognizer) {
print("A long press has been detected.")
let touchedAt = recognizer.location(in: self.map) // adds the location on the view it was pressed
let touchedAtCoordinate : CLLocationCoordinate2D = map.convert(touchedAt, toCoordinateFrom: self.map) // will get coordinates
let newPin = MKPointAnnotation()
newPin.coordinate = touchedAtCoordinate
map.addAnnotation(newPin)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
map.removeAnnotation(newPin)
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))
//set region on the map
map.setRegion(region, animated: true)
newPin.coordinate = location.coordinate
map.addAnnotation(newPin)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You can use custom location manager class and call the singleton function in didfinishlaunching with option and save latitude and longitude in UserDefault .Set camera position in viewDidLoad for mapView class
1.Make singleton class
var locationShareInstance:locationManagerClass = locationManagerClass()
class locationManagerClass: NSObject, CLLocationManagerDelegate, WebServiceDelegate , UIAlertViewDelegate
{
var locationManager = CLLocationManager()
class func sharedLocationManager() -> locationManagerClass
{
locationShareInstance = locationManagerClass()
return locationShareInstance
}
func startStandardUpdates() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.activityType = .automotiveNavigation
locationManager.distanceFilter = 10
locationManager.pausesLocationUpdatesAutomatically = false
if (Bundle.main.object(forInfoDictionaryKey: "NSLocationWhenInUseUsageDescription") != nil) {
locationManager.requestWhenInUseAuthorization()
}
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// If it's a relatively recent event, turn off updates to save power.
let location: CLLocation = locations.last!
let strLocation = "\(location.coordinate.latitude)"
if strLocation == "" {
}else{
UserDefaults.standard.set("\(location.coordinate.latitude)", forKey: "lat")
UserDefaults.standard.set("\(location.coordinate.longitude)", forKey: "long")
UserDefaults.standard.synchronize()
debugPrint("Spedd: \(location.speed)")
// self.updateLocationToServer()
self.stopStandardUpdate()
}
}
func stopStandardUpdate(){
locationManager.stopUpdatingLocation()
}
//MARK:- WHEN DENIED
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == CLAuthorizationStatus.denied {
NSLog("DENIAL")
UserDefaults.standard.set("\(0.0)", forKey: "lat")
UserDefaults.standard.set("\(0.0)", forKey: "long")
self.generateAlertToNotifyUser()
}
}
func generateAlertToNotifyUser() {
if CLLocationManager.authorizationStatus() == CLAuthorizationStatus.notDetermined{
var title: String
title = ""
let message: String = "Location Services are not able to determine your location"
let alertView: UIAlertView = UIAlertView(title: title, message: message, delegate: self, cancelButtonTitle: "Cancel", otherButtonTitles: "Settings")
alertView.show()
}
if CLLocationManager.authorizationStatus() == CLAuthorizationStatus.denied{
var title: String
title = "Location services are off"
let message: String = "To post spots or find near by spots, you must turn on Location Services from Settings"
let alertView: UIAlertView = UIAlertView(title: title, message: message, delegate: self, cancelButtonTitle: "Cancel", otherButtonTitles: "Settings")
alertView.show()
}
if CLLocationManager.authorizationStatus() == CLAuthorizationStatus.notDetermined
{
startStandardUpdates()
}
}
}
Call this functions in didfinishlaunchingwithoption
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let locationManager = locationManagerClass.sharedLocationManager()
locationManager.startStandardUpdates()
}
3.Set camera in viewDidLoad of your class
if UserDefaults.standard.object(forKey: "lat") != nil {
let lat = UserDefaults.standard.object(forKey: "lat") as! String
let long = UserDefaults.standard.object(forKey: "long") as! String
var userLoc = CLLocationCoordinate2D()
userLoc.latitude = CDouble(lat)!
userLoc.longitude = CDouble(long)!
let span = MKCoordinateSpanMake(0.02, 0.02)
let region = MKCoordinateRegion(center: userLoc, span: span)
mapVw.setRegion(region, animated: true)
mapVw.showsUserLocation = true
}

How to send current location to another iPhone user swift?

I want to be able to send my current location to another iPhone user. I can get my current location and continuously update my location to see where I am on the map but how could I send this info to another iPhone user so they can see where i am in real time?
The basic structure of code is:
import UIKit
import MapKit
class ViewController: UIViewController,CLLocationManagerDelegate {
#IBOutlet weak var myMapView: MKMapView!
let myLocMgr = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
myLocMgr.desiredAccuracy = kCLLocationAccuracyBest
myLocMgr.requestWhenInUseAuthorization()
myLocMgr.startUpdatingLocation()
myLocMgr.delegate = self
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// get most recient coordinate
let myCoor = locations[locations.count - 1]
//get lat & long
let myLat = myCoor.coordinate.latitude
let myLong = myCoor.coordinate.longitude
let myCoor2D = CLLocationCoordinate2D(latitude: myLat, longitude: myLong)
//set span
let myLatDelta = 0.05
let myLongDelta = 0.05
let mySpan = MKCoordinateSpan(latitudeDelta: myLatDelta, longitudeDelta: myLongDelta)
let myRegion = MKCoordinateRegion(center: myCoor2D, span: mySpan)
//center map at this region
myMapView.setRegion(myRegion, animated: true)
//add anotation
let myAnno = MKPointAnnotation()
myAnno.coordinate = myCoor2D
myMapView.addAnnotation(myAnno)
}