Add the camera on current position of the user on the map - ios

I added this code in my viewController
import UIKit
import CoreLocation
import GoogleMaps
class CourseClass2: UIViewController, UITableViewDelegate, UITableViewDataSource, UINavigationControllerDelegate {
#IBOutlet weak var mapView: GMSMapView!
}
and this extension
extension CourseClass2: CLLocationManagerDelegate {
func determineMyCurrentLocation() {
guard currentLocation == nil else {
return
}
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
locationManager?.requestWhenInUseAuthorization()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation:CLLocation = locations[0] as CLLocation
manager.stopUpdatingLocation()
print("user latitude = \(userLocation.coordinate.latitude)")
print("user longitude = \(userLocation.coordinate.longitude)")
didReceiveUserLocation(userLocation)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error \(error)")
errorGettingCurrentLocation(error.localizedDescription)
}
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse || status == .authorizedAlways {
locationManager?.startUpdatingLocation()
//locationManager.startUpdatingHeading()
} else if status == .denied || status == .restricted {
errorGettingCurrentLocation("Location access denied")
}
}
func errorGettingCurrentLocation(_ errorMessage:String) {
let alert = UIAlertController.init(title: "Error", message: errorMessage, preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
}
to get the user location. Now i would like to add in the viewDidLoad of my controller a camera like let camera = GMSCameraPosition.camera(withLatitude: , longitude: , zoom: 14) on the user position, but how can i do to use in this line the latitude and longitude of my user current position that i found with my extension?

You need to call locationManager.startUpdatingLocation() in viewDidLoad() where locationManager is CLLocationManager object. As soon as your current location is retrieved, didUpdateLocations delegate method is called where you can set the camera position as follows:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation = locations.last
let location = GMSCameraPosition.camera(withLatitude: userLocation!.coordinate.latitude, longitude: userLocation!.coordinate.longitude, zoom: 16.0)
mapView.animate(to: location)
}

Related

how to convey the coordinate from didUpdateLocation to some view controller?

I am trying to make LocationService class that will convey userCoordinate, so it will be reusable for more than one View Controller
import UIKit
import CoreLocation
class LocationService: NSObject {
let manager = CLLocationManager()
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.startUpdatingLocation()
}
func getPermission() {
// to ask permission to the user by showing an alert (the alert message is available on info.plist)
if CLLocationManager.authorizationStatus() == .notDetermined {
manager.requestWhenInUseAuthorization()
}
}
func checkLocationAuthorizationStatus() -> CLAuthorizationStatus{
return CLLocationManager.authorizationStatus()
}
}
extension LocationService : CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
manager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("location is not available: \(error.localizedDescription)")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let userLocation = locations.first else {return}
if userLocation.horizontalAccuracy > 0 {
manager.stopUpdatingLocation()
let coordinate = Coordinate(location: userLocation)
// how to convey this coordinate for several view controller?
}
}
}
as you can see in the didUpdateLocations method that comes from CLLocationManagerDelegate, the coordinate need some time to be generated.
but I don't know how to convey that user coordinate, I think it will use completion handler but I don't know how to get that
so let say in HomeVC, I will call that LocationService to get the userCoordinate
import UIKit
class HomeVC: UIViewController {
let locationService = LocationService()
override func viewDidLoad() {
super.viewDidLoad()
// get coordinate, something like this
locationService.getCoordinate()
}
}
You can use Notification like Paulw11 said. You need to update didUpdateLocations. This is the place where you are going to post notification.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let userLocation = locations.first else {return}
if userLocation.horizontalAccuracy > 0 {
manager.stopUpdatingLocation()
let coordinate = Coordinate(location: userLocation)
let locationDictionary: [String: Double] = ["lat": location.coordinate.latitude,
"long": location.coordinate.longitude]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "YourNotificationNameAsYouWantToNameIt"), object: nil, userInfo: locationDictionary)
}
}
Now in viewDidLoad in every view controller that you want this location you need to observe this notification:
NotificationCenter.default.addObserver(self, selector: #selector(doSomethingAboutLocation(_:)), name: NSNotification.Name(rawValue: "YourNotificationNameAsYouWantToNameIt"), object: nil)
Then access your location like this in your function called from selector:
#objc func doSomethingAboutLocation(_ notification: Notification) {
if let notificationInfo = notification.userInfo {
let coordinate = CLLocation(latitude: notificationInfo["lat"] as! Double, longitude: notificationInfo["long"] as! Double)
// use your coordinate as you want
}
}

Difficult to show a marker on my current position on the map

Following different tutorial i added a map view in my controller
import UIKit
import CoreLocation
import GoogleMaps
class CourseClass2: UIViewController, UITableViewDelegate, UITableViewDataSource, UINavigationControllerDelegate {
#IBOutlet weak var mapView: GMSMapView!
}
and also this extension
extension CourseClass2: CLLocationManagerDelegate {
func determineMyCurrentLocation() {
guard currentLocation == nil else {
return
}
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
locationManager?.requestWhenInUseAuthorization()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation:CLLocation = locations[0] as CLLocation
manager.stopUpdatingLocation()
print("user latitude = \(userLocation.coordinate.latitude)")
print("user longitude = \(userLocation.coordinate.longitude)")
didReceiveUserLocation(userLocation)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error \(error)")
errorGettingCurrentLocation(error.localizedDescription)
}
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse || status == .authorizedAlways {
locationManager?.startUpdatingLocation()
//locationManager.startUpdatingHeading()
} else if status == .denied || status == .restricted {
errorGettingCurrentLocation("Location access denied")
}
}
func errorGettingCurrentLocation(_ errorMessage:String) {
let alert = UIAlertController.init(title: "Error", message: errorMessage, preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
}
now how can i show in my mapView, my position with a marker on it? I'm getting confusing about it because i have an extension unlike the tutorials i found where the code is all in the class.

Showing multiple Marker in map

i am making an app with google map it is showing multiple marker on map i want to show only for current location.
//
import UIKit
import GoogleMaps
import GooglePlaces
import GooglePlacePicker
class HomeLocationVC: UIViewController{
#IBOutlet var addressTextField: UITextField!
#IBOutlet var mapViewContainer: UIView!
var locationManager = CLLocationManager()
var currentLocation: CLLocation?
var mapView: GMSMapView!
var placesClient: GMSPlacesClient!
var zoomLevel: Float = 15.0
var likelyPlaces: [GMSPlace] = []
var selectedPlace: GMSPlace?
var camera:GMSCameraPosition?
var marker = GMSMarker()
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 50
locationManager.startUpdatingLocation()
locationManager.delegate = self
placesClient = GMSPlacesClient.shared()
userCurrentLocation()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func searchWIthAddress(_ sender: Any) {
// Prepare the segue.
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueToSelect" {
if let nextViewController = segue.destination as? PlacesViewController {
nextViewController.likelyPlaces = likelyPlaces
}
}
}
}
}
extension HomeLocationVC: CLLocationManagerDelegate {
// Handle incoming location events.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first{
print("Location: \(location)")
camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude,
longitude: location.coordinate.longitude,
zoom: zoomLevel)
if mapView.isHidden {
mapView.isHidden = false
mapView.camera = camera!
} else {
mapView.animate(to: camera!)
}
listLikelyPlaces()
locationManager.stopUpdatingLocation()
}
let position = CLLocationCoordinate2D(latitude: (locations.last?.coordinate.latitude)!, longitude: (locations.last?.coordinate.longitude)!)
marker = GMSMarker(position: position)
marker.title = "Location"
marker.map = self.mapView
// marker.isTappable = true
}
// Handle authorization for the location manager.
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
print("Location access was restricted.")
case .denied:
print("User denied access to location.")
// Display the map using the default location.
mapView.isHidden = false
case .notDetermined:
print("Location status not determined.")
case .authorizedAlways: fallthrough
case .authorizedWhenInUse:
locationManager.startUpdatingLocation()
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
print("Location status is OK.")
}
}
// Handle location manager errors.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
print("Error: \(error)")
}
}
extension HomeLocationVC: GMSMapViewDelegate{
func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) {
reverseGeocodeCoordinate(coordinate: position.target)
}
}
Each time func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) is called you ADD a marker. Even by calling locationManager.stopUpdatingLocation() there could still be pending updates.
You should keep a reference to a single marker and update it's position property instead.
so add a stored property to the class
var marker: GSMMarker?
and then each you receive new location updates just update it.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let position = CLLocationCoordinate2D(latitude: (locations.last?.coordinate.latitude)!, longitude: (locations.last?.coordinate.longitude)!)
if let marker = self.marker {
marker = GMSMarker(position: position)
}
}
Note: there's a stray bracket in your code above, I imagine it will just be a copy error, but its just under locationManager.stopUpdatingLocation() in the func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) function

How do I make "Center location on Map" work?

I am new to programing.
I have found the code below and it does everything but center location.(zoom in, map, blue dot all work.)
If I run in simulator, (city run) the blue dot just runs off the page.
import UIKit
import MapKit
import CoreLocation
import Foundation
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var map: MKMapView!
var locationManager: CLLocationManager?
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager!.delegate = self
map.showsUserLocation = true
if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
locationManager!.startUpdatingLocation()
} else {
locationManager!.requestWhenInUseAuthorization()
}
}
private func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
print("NotDetermined")
case .restricted:
print("Restricted")
case .denied:
print("Denied")
case .authorizedAlways:
print("AuthorizedAlways")
case .authorizedWhenInUse:
print("AuthorizedWhenInUse")
locationManager!.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.first!
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate, 500, 500)
map.setRegion(coordinateRegion, animated: true)
locationManager?.stopUpdatingLocation()
locationManager = nil
}
}
Delete your implementation of
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
Just get authorization, set map.showsUserLocation = true, and stand back.
Here is my Swift 3 code that works and is tested. It loads mapView, ask user's permission and zoom in into his location.
import UIKit
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.activityType = .automotiveNavigation
locationManager.distanceFilter = 10.0
mapView.showsUserLocation = true
mapView.mapType = .standard
self.mapView.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation:CLLocation = locations[0] as CLLocation
locationManager.stopUpdatingLocation()
let location = CLLocationCoordinate2D(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let span = MKCoordinateSpanMake(0.05, 0.05)
let region = MKCoordinateRegion (center: location,span: span)
mapView.setRegion(region, animated: true)
}
}

Repeating location data with CLLocation

This is a really basic outlay of what I am using...
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
// Call the locationManager class
let LocationManager = CLLocationManager()
// CoreData Delegate
let appDelegate = UIApplication.shared.delegate as! AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
// Conform to Delegate Method
self.LocationManager.delegate = self
// Set required accuracy
self.LocationManager.desiredAccuracy = kCLLocationAccuracyBest
// Blue dot
self.mapView.showsUserLocation = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// check location services active
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// check location services
switch CLLocationManager.authorizationStatus() {
case .authorizedAlways:
self.LocationManager.startUpdatingLocation()
case .notDetermined:
self.LocationManager.requestAlwaysAuthorization()
case .authorizedWhenInUse, .restricted, .denied:
let alertController = UIAlertController(
title: "Background Location Access Disabled",
message: "In order to work your location settings need to be set to 'Always'.",
preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
let openAction = UIAlertAction(title: "Open Settings", style: .default) { (action) in
if let url = NSURL(string:UIApplicationOpenSettingsURLString) {
UIApplication.shared.open(url as URL)
}
}
alertController.addAction(openAction)
self.present(alertController, animated: true, completion: nil)
}
}
// Location delegate methods
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(locations)
// get last location
let location = locations.last
print(location!.coordinate.latitude)
// set region
let region = MKCoordinateRegion(center: location!.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
// deploy region to map
self.mapView.setRegion(region, animated: true)
// Map to follow the user
self.mapView.setUserTrackingMode(MKUserTrackingMode.follow, animated: true)
// Show compass on map
self.mapView.showsCompass = true
// save the location data to CoreData
//self.save(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
// end Location updating
self.LocationManager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Errors: " + error.localizedDescription)
}
}
My issue is that func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation] calls itself over and over again (around 3 times on initial load)...
I am using the .last which AFAIK is meant to pull out the last result in that object.. which it probably is, as with breakpoints inserted it after the first 2 prints it only returns 1 lot of results...
After searching high and low, I am hoping I can get a result by asking the question... Thanks!
Console output of my issue:
When you call startUpdatingLocation() the location manager immediately starts the delivering of location data. The first incoming locations may be way off your actual location, so check the horizontalAccuracy and verticalAccuracy attributes and dismiss locations which are too inaccurate.
It looks like you just want to get a one-shot location, if so try this code:
// Use:
// at class level:
// var manager: LocationOneShotManager?
// in viewDidLoad:
// manager = LocationOneShotManager()
// manager!.fetchWithCompletion {location, error in
// // fetch location or an error
// if let loc = location {
// println(location)
// } else if let err = error {
// println(err.localizedDescription)
// }
// self.manager = nil
// }
import UIKit
import CoreLocation
// possible errors
enum OneShotLocationManagerErrors: Int {
case AuthorizationDenied
case AuthorizationNotDetermined
case InvalidLocation
}
class LocationOneShotManager: NSObject, CLLocationManagerDelegate {
// location manager
private var locationManager: CLLocationManager?
// destroy the manager
deinit {
locationManager?.delegate = nil
locationManager = nil
}
typealias LocationClosure = ((location: CLLocation?, error: NSError?)->())
private var didComplete: LocationClosure?
// location manager returned, call didcomplete closure
private func _didComplete(location: CLLocation?, error: NSError?) {
locationManager?.stopUpdatingLocation()
didComplete?(location: location, error: error)
locationManager?.delegate = nil
locationManager = nil
}
// location authorization status changed
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .AuthorizedWhenInUse:
self.locationManager!.startUpdatingLocation()
case .Denied:
_didComplete(nil, error: NSError(domain: self.classForCoder.description(),
code: OneShotLocationManagerErrors.AuthorizationDenied.rawValue,
userInfo: nil))
default:
break
}
}
internal func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
_didComplete(nil, error: error)
}
internal func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
_didComplete(location, error: nil)
}
// ask for location permissions, fetch 1 location, and return
func fetchWithCompletion(completion: LocationClosure) {
// store the completion closure
didComplete = completion
// fire the location manager
locationManager = CLLocationManager()
locationManager!.delegate = self
// check for description key and ask permissions
if (NSBundle.mainBundle().objectForInfoDictionaryKey("NSLocationWhenInUseUsageDescription") != nil) {
locationManager!.requestWhenInUseAuthorization()
} else if (NSBundle.mainBundle().objectForInfoDictionaryKey("NSLocationAlwaysUsageDescription") != nil) {
locationManager!.requestAlwaysAuthorization()
} else {
fatalError("To use location in iOS8 you need to define either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in the app bundle's Info.plist file")
}
}
}

Resources