I am trying to create a function in my app that will guide the user to a marker I have created.
This is the code I am using, it works great, It gets the users current location and show it on the map. But how can I get a directions to a marker?
Any awnser will be helpful
class Karta: UIViewController, CLLocationManagerDelegate {
#IBOutlet var mapView: GMSMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
//allow app to track user
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
//set out a marker on the map
var marker = GMSMarker()
marker.position = CLLocationCoordinate2DMake(56.675907, 12.858798)
marker.appearAnimation = kGMSMarkerAnimationPop
marker.icon = UIImage(named: "flag_icon")
marker.map = mapView
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Types Segue" {
let navigationController = segue.destinationViewController as UINavigationController
}
}
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
//If map is being used
if status == .AuthorizedWhenInUse {
var myLocation = mapView
locationManager.startUpdatingLocation()
mapView.myLocationEnabled = true
mapView.settings.myLocationButton = true
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
if let location = locations.first as? CLLocation {
mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
locationManager.stopUpdatingLocation()
}
}
}
So i recently just solved this issue, here is my Swift 3 implementation using the latest version of Alamofire (4.3)
func fetchMapData() {
let directionURL = "https://maps.googleapis.com/maps/api/directions/json?" +
"origin=\(originAddressLat),\(originAddressLng)&destination=\(destinationAddressLat),\(destinationAddressLong)&" +
"key=YOUROWNSERVERKEY"
Alamofire.request(directionURL).responseJSON
{ response in
if let JSON = response.result.value {
let mapResponse: [String: AnyObject] = JSON as! [String : AnyObject]
let routesArray = (mapResponse["routes"] as? Array) ?? []
let routes = (routesArray.first as? Dictionary<String, AnyObject>) ?? [:]
let overviewPolyline = (routes["overview_polyline"] as? Dictionary<String,AnyObject>) ?? [:]
let polypoints = (overviewPolyline["points"] as? String) ?? ""
let line = polypoints
self.addPolyLine(encodedString: line)
}
}
}
func addPolyLine(encodedString: String) {
let path = GMSMutablePath(fromEncodedPath: encodedString)
let polyline = GMSPolyline(path: path)
polyline.strokeWidth = 5
polyline.strokeColor = .blue
polyline.map = whateverYourMapViewObjectIsCalled
}
Disclaimer:Swift 2
func addOverlayToMapView(){
let directionURL = "https://maps.googleapis.com/maps/api/directions/json?origin=\(srcLocation.coordinate.latitude),\(srcLocation.coordinate.longitude)&destination=\(destLocation.coordinate.latitude),\(destLocation.coordinate.longitude)&key=Your Server Key"
Alamofire.request(.GET, directionURL, parameters: nil).responseJSON { response in
switch response.result {
case .Success(let data):
let json = JSON(data)
print(json)
let errornum = json["error"]
if (errornum == true){
}else{
let routes = json["routes"].array
if routes != nil{
let overViewPolyLine = routes![0]["overview_polyline"]["points"].string
print(overViewPolyLine)
if overViewPolyLine != nil{
self.addPolyLineWithEncodedStringInMap(overViewPolyLine!)
}
}
}
case .Failure(let error):
print("Request failed with error: \(error)")
}
}
}
Using the points, we now create the Path from two points using fromEncodedPath
func addPolyLineWithEncodedStringInMap(encodedString: String) {
let path = GMSMutablePath(fromEncodedPath: encodedString)
let polyLine = GMSPolyline(path: path)
polyLine.strokeWidth = 5
polyLine.strokeColor = UIColor.yellowColor()
polyLine.map = mapView
}
Unlike Apple's MapKit, the Google Maps SDK for iOS does not natively include a way to perform route calculations.
Instead, you need to use the Google Directions API: https://developers.google.com/maps/documentation/directions/. It is an HTTP-only API, and Google does not provide any SDK as of today, but you can easily write your own wrapper yourself, or choose one of the many options available on Github:
https://github.com/sudeepjaiswal/GoogleDirections
https://github.com/sebk/GoogleDirections
https://github.com/iamamused/iOS-DirectionKit
https://github.com/marciniwanicki/OCGoogleDirectionsAPI
and many others...
Its Very Simple
if you added a Google map SDK in your iOS Project and if you want to implement get google map direction lines between two different directions i have made as demo code in simple way understand using swift 2.3 try it, modify it and use it.!!!
Note: Dont Forgot to change your lat long you want and API Key (You may Use API key with None Restrictions on Google API Manager Credential Section)
func callWebService(){
let url = NSURL(string: "https://maps.googleapis.com/maps/api/directions/json?origin=\(18.5235),\(73.7184)&destination=\(18.7603),\(73.8630)&key=AIzaSyDxSgGQX6jrn4iq6dyIWAKEOTneZ3Z8PtU")
let request = NSURLRequest(URL: url!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
// notice that I can omit the types of data, response and error
do{
if let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary {
//print(jsonResult)
let routes = jsonResult.valueForKey("routes")
//print(routes)
let overViewPolyLine = routes![0]["overview_polyline"]!!["points"] as! String
print(overViewPolyLine)
if overViewPolyLine != ""{
//Call on Main Thread
dispatch_async(dispatch_get_main_queue()) {
self.addPolyLineWithEncodedStringInMap(overViewPolyLine)
}
}
}
}
catch{
print("Somthing wrong")
}
});
// do whatever you need with the task e.g. run
task.resume()
}
func addPolyLineWithEncodedStringInMap(encodedString: String) {
let camera = GMSCameraPosition.cameraWithLatitude(18.5204, longitude: 73.8567, zoom: 10.0)
let mapView = GMSMapView.mapWithFrame(CGRect.zero, camera: camera)
mapView.myLocationEnabled = true
let path = GMSMutablePath(fromEncodedPath: encodedString)
let polyLine = GMSPolyline(path: path)
polyLine.strokeWidth = 5
polyLine.strokeColor = UIColor.yellowColor()
polyLine.map = mapView
let smarker = GMSMarker()
smarker.position = CLLocationCoordinate2D(latitude: 18.5235, longitude: 73.7184)
smarker.title = "Lavale"
smarker.snippet = "Maharshtra"
smarker.map = mapView
let dmarker = GMSMarker()
dmarker.position = CLLocationCoordinate2D(latitude: 18.7603, longitude: 73.8630)
dmarker.title = "Chakan"
dmarker.snippet = "Maharshtra"
dmarker.map = mapView
view = mapView
}
Related
i have 3 annotation and i draw polyline between first and second annotation but i need the therd one move over that polyline but it's always move in street polyline to the destnation
-my code
func moveDelivery(_ destinationCoordinate : CLLocationCoordinate2D{
self.deliveryAnnotation.coordinate = CLLocationCoordinate2DMake(29.959640, 31.270421)
let sourcePlaceMark = MKPlacemark(coordinate: self.userAnnotation.coordinate)
//sourcePlaceMark.title
let destPlaceMkark = MKPlacemark(coordinate: self.deliveryAnnotation.coordinate)
let sourceItem = MKMapItem(placemark: sourcePlaceMark)
let destItem = MKMapItem(placemark: destPlaceMkark)
let directionRequest = MKDirections.Request()
directionRequest.source = sourceItem
directionRequest.destination = destItem
directionRequest.transportType = .any
let direction = MKDirections(request: directionRequest)
direction.calculate(completionHandler: {
response, error in
guard let response = response else {
if let error = error {
print(error.localizedDescription)
} else {
self.deliveryAnnotation.courseDegrees = self.getHeadingForDirectionFromCoordinate(self.kitchenAnnotation.coordinate, toLoc: self.userAnnotation.coordinate)
self.view.transform = CGAffineTransform(rotationAngle:CGFloat(self.deliveryAnnotation.courseDegrees))
}
return
}
guard let primaryRoute = response.routes.first else { return }
let route = response.routes[0]
self.mapView.addOverlay(route.polyline, level: .aboveRoads)
let rekt = route.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegion(rekt), animated: true)
})
//
UIView.animate(withDuration: Double(60), animations: {
self.deliveryAnnotation.coordinate = destinationCoordinate
}, completion: { success in
if success {
}
})
}
Your third annotation isn't following the route because you're animating it moving in a straight line between the first and second line. Try getting the coordinates from the MKRoute's polyline and animate between each one (According to apple's docs MKRoutes are made up of coordinates, but you might be able to use points as well)
If you'd like it to animate over the span of 60 seconds:
func moveDelivery(_ destinationCoordinate: CLLocationCoordinate2D) {
// I don't know why you have the delivery annotation start here, is this for testing?
deliveryAnnotation.coordinate = CLLocationCoordinate2DMake(29.959640, 31.270421)
let sourcePlaceMark = MKPlacemark(coordinate: destinationCoordinate)
let destPlaceMkark = MKPlacemark(coordinate: userAnnotation.coordinate)
let directionRequest = MKDirections.Request()
directionRequest.source = MKMapItem(placemark: sourcePlaceMark)
directionRequest.destination = MKMapItem(placemark: destPlaceMkark)
directionRequest.transportType = .any
let direction = MKDirections(request: directionRequest)
direction.calculate(completionHandler: {
response, error in
guard let response = response else {
print("MKRequest gave no response")
if let error = error {
print(error.localizedDescription)
} else {
self.deliveryAnnotation.courseDegrees = self.getHeadingForDirectionFromCoordinate(self.kitchenAnnotation.coordinate, toLoc: self.userAnnotation.coordinate)
self.view.transform = CGAffineTransform(rotationAngle:CGFloat(self.deliveryAnnotation.courseDegrees))
}
return
}
guard let primaryRoute = response.routes.first else {
print("response has no routes")
return
}
self.mapView.addOverlay(primaryRoute.polyline, level: .aboveRoads)
let rekt = primaryRoute.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegion(rekt), animated: true)
let coordinateArray = primaryRoute.polyline.coordinates
assert(coordinateArray.count > 0, "coordinate array is empty")
self.routeCoordinates = coordinateArray
// initiate recursive animations
self.coordinateIndex = 0
})
}
var routeCoordinates = [CLLocationCoordinate2D]()
var avgAnimationTime: Double {
return 60 / Double(routeCoordinates.count)
}
var coordinateIndex: Int! {
didSet {
guard coordinateIndex != routeCoordinates.count else {
print("animated through all coordinates, stopping function")
return
}
animateToNextCoordinate()
}
}
func animateToNextCoordinate() {
let coordinate = routeCoordinates[coordinateIndex]
UIView.animate(withDuration: avgAnimationTime, animations: {
self.deliveryAnnotation.coordinate = coordinate
}, completion: { _ in
self.coordinateIndex += 1
print("moved between coordinates")
})
}
EDIT
make sure to include this extension, otherwise you won't be able to get the coordinates of the MKRoute (source: https://gist.github.com/freak4pc/98c813d8adb8feb8aee3a11d2da1373f)
public extension MKMultiPoint {
var coordinates: [CLLocationCoordinate2D] {
var coords = [CLLocationCoordinate2D](repeating: kCLLocationCoordinate2DInvalid,
count: pointCount)
getCoordinates(&coords, range: NSRange(location: 0, length: pointCount))
return coords
}
}
EDIT #2
See above, edited original answer to animate through each coordinate after the previous finishes animating. Really rough but it should work.
EDIT #3
Added your code to get the destination variable as well as some assert and debug printing calls. If things aren't working this time, please tell me which debug messages you get.
EDIT #4
I just demo'd my code and it works. Here is the MapViewController class I used along with necessary extensions:
private let reuseId = "deliveryReuseId"
private let userTitle = "user"
private let startingPointTitle = "store"
private let deliveryTitle = "delivery truck"
class MapViewController: UIViewController {
var mapView: MKMapView!
// annotations for this demo, replace with your own annotations
var deliveryAnnotation: MKPointAnnotation = {
let annotation = MKPointAnnotation()
annotation.title = deliveryTitle
return annotation
}()
let userAnnotation: MKPointAnnotation = {
let annotation = MKPointAnnotation()
annotation.title = userTitle
annotation.coordinate = CLLocationCoordinate2DMake(29.956694, 31.276854)
return annotation
}()
let startingPointAnnotation: MKPointAnnotation = {
let annotation = MKPointAnnotation()
annotation.title = startingPointTitle
annotation.coordinate = CLLocationCoordinate2DMake(29.959622, 31.270363)
return annotation
}()
override func viewDidLoad() {
super.viewDidLoad()
loadMapView()
navigate()
}
func loadMapView() {
// set map
mapView = MKMapView()
view = mapView
mapView.delegate = self
mapView.register(MKAnnotationView.self, forAnnotationViewWithReuseIdentifier: reuseId)
// add annotations
mapView.addAnnotation(userAnnotation)
mapView.addAnnotation(startingPointAnnotation)
mapView.addAnnotation(deliveryAnnotation)
}
func navigate() {
let sourcePlaceMark = MKPlacemark(coordinate: startingPointAnnotation.coordinate)
let destPlaceMkark = MKPlacemark(coordinate: userAnnotation.coordinate)
let directionRequest = MKDirections.Request()
directionRequest.source = MKMapItem(placemark: sourcePlaceMark)
directionRequest.destination = MKMapItem(placemark: destPlaceMkark)
directionRequest.transportType = .any
let direction = MKDirections(request: directionRequest)
direction.calculate(completionHandler: { response, error in
if let error = error {
print(error.localizedDescription)
return
}
guard let primaryRoute = response!.routes.first else {
print("response has no routes")
return
}
self.mapView.addOverlay(primaryRoute.polyline, level: .aboveRoads)
self.mapView.setRegion(MKCoordinateRegion(primaryRoute.polyline.boundingMapRect), animated: true)
// initiate recursive animation
self.routeCoordinates = primaryRoute.polyline.coordinates
self.coordinateIndex = 0
})
}
var routeCoordinates = [CLLocationCoordinate2D]()
var avgAnimationTime: Double {
// to show delivery in 60 second, replace 60 with amount of seconds you'd like to show
return 60 / Double(routeCoordinates.count)
}
var coordinateIndex: Int! {
didSet {
guard coordinateIndex != routeCoordinates.count else {
print("animated through all coordinates, stopping function")
return
}
animateToNextCoordinate()
}
}
func animateToNextCoordinate() {
let coordinate = routeCoordinates[coordinateIndex]
UIView.animate(withDuration: avgAnimationTime, animations: {
self.deliveryAnnotation.coordinate = coordinate
}, completion: { _ in
self.coordinateIndex += 1
})
}
}
extension MapViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
// replace these images with your own
switch annotation.title {
case userTitle:
annotationView.image = UIImage(named: "user")
case startingPointTitle:
annotationView.image = UIImage(named: "store")
case deliveryTitle:
annotationView.image = UIImage(named: "deliveryTruck")
default: break
}
return annotationView
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
guard overlay is MKPolyline else {
return MKOverlayRenderer()
}
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = .black
renderer.lineWidth = 5
renderer.lineJoin = .round
return renderer
}
}
public extension MKMultiPoint {
var coordinates: [CLLocationCoordinate2D] {
var coords = [CLLocationCoordinate2D](repeating: kCLLocationCoordinate2DInvalid,
count: pointCount)
getCoordinates(&coords, range: NSRange(location: 0, length: pointCount))
return coords
}
}
I'm learning swift.
I have a json from server.
[
{
"dId": 1,
"vendor": {
"vendorId": 1,
"name": "Gems",
"about": "Get Good quality stones",
"address": "JacksonVille Road",
"latitude": 12.232323,
"longitude": 77.230802,
},
"name": "Gems Delight",
}
]
I'm unable to parse this json and get the data from lat and lon and name from vendor.
My url method and my for loop how can I exactly get the latitude and longitude from the loop and put them on the map ?
My View did load method
override func viewDidLoad() {
super.viewDidLoad()
guard let gitUrl = URL(string: "localhost:8080/deals") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response
, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
var lat:Double
var lon:Double
var nam = ""
for items in json as! [AnyObject]{
// let te = type(of: items)
// print(te)
let new = items["vendor"]
for (it,key) in new as! [String:Any]{
// print(it,key)
// print(it["longitude"])
if it == "longitude"{
print(it,key)
lon = key as! Double
}
if it == "latitude"{
print(it,key)
lat = key as! Double
}
if it == "name"{
nam = key as! String
}
if (nam.isEmpty == false){
print("falsdalsdasldasd")
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
let camera = GMSCameraPosition.camera(withLatitude:lat, longitude:lon, zoom: 4.0)
let subView = GMSMapView.map(withFrame: self.view.bounds,camera:camera)
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude:lat, longitude:lon)
marker.title = nam
// marker.snippet = "Australia"
marker.map = subView
self.mapView.addSubview(subView)
}
// print(it["longitude"])
}
// let te = type(of: items)
// for it in new as![AnyObject]{
//// print(it"])
// print(it)
//
// }
// print(items["vendor"].["latitude"])
// print(items.vendor)
// print(items[""])
// let nam = items["name"];
// let desc = items["description"];
// self.locationNames.append(nam as! String)
// self.locationDescription.append(desc as! String)
//
}
// self.colecVw.delegate = self
// self.colecVw.dataSource = self
// self.colecVw.reloadData()
// }
} catch let err {
print("Err", err)
}
}.resume()
print("coming here")
// Create a GMSCameraPosition that tells the map to display the
// coordinate -33.86,151.20 at zoom level 6.
// let mapView = GMSMapView(frame: CGRect(x: 0, y: 64, width: self.currentDeviceSize.width, height: self.bottomBgView.frame.minY - 64))
// view = mapView
//
// // Creates a marker in the center of the map.
// let marker = GMSMarker()
// marker.position = CLLocationCoordinate2D(latitude: -33.86, longitude: 151.20)
// marker.title = "Sydney"
// marker.snippet = "Australia"
// marker.map = mapView
}
Please help me as I'm learning to code my code might not be correct can please dont mind and load the data of lat and long onto the map. Should I construct an object and put them there ?
Variable 'lon' used before being initialized
Variable 'lat' used before being initialized
This is the error I get from the code.
The errors occur because lat and lon must have a value in the line
let camera = GMSCameraPosition.camera(withLatitude:lat, longitude:lon, zoom: 4.0)
which is not guaranteed if the key comparisons fail.
The condition is fulfilled if you write
var lat = 0.0 // The type Double is inferred
var lon = 0.0
But rather than enumerating the dictionary get the values for the keys directly and safely with optional binding
do {
// no mutableContainers !!
let json = try JSONSerialization.jsonObject(with: data) as! [[String:Any]]
for item in json {
if let vendor = item["vendor"] as? [String:Any],
let lat = vendor["latitude"] as? Double,
let lon = vendor["longitude"] as? Double,
let name = item["name"] as? String, !name.isEmpty {
print("falsdalsdasldasd")
// self.locationManager.delegate = self
// self.locationManager.requestWhenInUseAuthorization()
// self.locationManager.startUpdatingLocation()
let camera = GMSCameraPosition.camera(withLatitude:lat, longitude:lon, zoom: 4.0)
let subView = GMSMapView.map(withFrame: self.view.bounds,camera:camera)
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude:lat, longitude:lon)
marker.title = name
marker.map = subView
self.mapView.addSubview(subView)
}
}
} catch {
print("Err", error)
}
And it's nonsensical to call the LocationManager methods in each iteration. Call them once at the beginning of viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
...
You can try
struct Root: Codable {
let dID: Int
let vendor: Vendor
let name: String
enum CodingKeys: String, CodingKey {
case dID = "dId"
case vendor, name
}
}
struct Vendor: Codable {
let vendorID: Int
let name, about, address: String
let latitude, longitude: Double
enum CodingKeys: String, CodingKey {
case vendorID = "vendorId"
case name, about, address, latitude, longitude
}
}
--
let arr = try? JSONDecoder().decode([Root].self, from:data)
print(arr?.forEach {$0.vendor.latitude })
See this one
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
Pass json to
if let data = json as? NSArray {
for data in data {
if let data = data as? [String: AnyObject] {
let dataID = data["dId"] as? Int
if let data = data[“vendor”] as? [String: AnyObject] {
let vendorID = data["vendorId"] as? Int
}
}
}
}
I want to show user's profile pictures on map. I am storing datas on Firebase as longitude, latitude , photo as url on Firebase. Now, I am getting datas for all users on console. But I could not show these datas as annotation. Profile pictures will show on map according to longitude and latitude. How can I do this. I searched lots of websites and lots of tutorials. I could not find anything. All of shows that adding one picture on map as annotation. I want to do this for all users.`
class HomeTest: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var ref = Database.database().reference()
var storageRef = Storage.storage().reference()
let manager = CLLocationManager()
var users = [User] ()
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
fetchUser()
}
func fetchUser() {
Database.database().reference().child("locations").observe(.childAdded, with: {(snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User()
user.latitude = dictionary["latitude"] as! Float
user.longitude = dictionary["longitude"] as! Float
user.photo = dictionary["photo"] as! String
//user.username = dictionary["username"] as! String
print(user.latitude, user.longitude, user.photo)
self.users.append(user)
DispatchQueue.main.async(execute: {
// ???
})
}
} , withCancel: nil)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations [0]
let myLocation: CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let span:MKCoordinateSpan = MKCoordinateSpanMake(0.020,0.020)
let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation,span)
self.mapView.setRegion(region, animated: true)
self.mapView.isZoomEnabled = true
self.mapView.isScrollEnabled = true
self.mapView.showsUserLocation = true
}
}
I've just updated to the Xcode 9 and Swift 4 and it has broken at lot of my functions. One of which being how I get the user's location, zoom in on it on launch, detecting annotation selection and various other tasks. They don't work at all. My view controller class code is below.
import UIKit
import MapKit
import Firebase
import FirebaseDatabase
import Pulley
import CoreLocation
class ChildMapViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
let manager = CLLocationManager()
var isInitialized = false
func startBackgroundLocationUpdates() {
self.manager.delegate = self
manager.requestWhenInUseAuthorization()
manager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
manager.pausesLocationUpdatesAutomatically = true
manager.activityType = .fitness
manager.allowsBackgroundLocationUpdates = true
manager.startMonitoringSignificantLocationChanges()
manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
if !isInitialized {
isInitialized = true
let span:MKCoordinateSpan = MKCoordinateSpanMake(0.01, 0.01)
let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, span)
mapView.setRegion(region, animated: true)
self.mapView.showsUserLocation = true
}
}
struct loc {
let title: String
let latitude: Double
let longitude: Double
}
var locs = [
loc(title: "New York, NY", latitude: 40.713054, longitude: -74.007228),
]
func placeAnnotations() {
let annotations = locs.map { location -> MKAnnotation in
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
annotation.title = location.title
return annotation
}
mapView.addAnnotations(annotations)
}
var position:PulleyPosition!
func mapView(_ didSelectmapView: MKMapView, didSelect view: MKAnnotationView) {
Shared.shared.annotation = view.annotation!
if Shared.shared.annotation is MKUserLocation {
Shared.shared.nameString = "My Location"
} else {
loadData()
}
}
func mapView(_ mapView: MKMapView, didDeselect view:
MKAnnotationView) {
let drawerContent = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DrawerViewController")
if let drawer = self.parent?.parent as? PulleyViewController {
drawer.setDrawerContentViewController(controller: drawerContent, animated: false)
drawer.setDrawerPosition(position: PulleyPosition.partiallyRevealed, animated: true)
}
}
func loadData() {
let number = (Shared.shared.annotation?.title)!
if let number = number {
let ref = FIRDatabase.database().reference()
ref.child("safehouses").child("\(number)").observeSingleEvent(of: .value, with: { snapshot in
let snapDict = snapshot.value as? NSDictionary
let name = snapDict?["name"] as? String
let phone = snapDict?["phone"] as? String
let current = CLLocation(latitude: (self.manager.location?.coordinate.latitude)!, longitude: (self.manager.location?.coordinate.longitude)!)
let destination = CLLocation(latitude: Shared.shared.annotation.coordinate.latitude, longitude: Shared.shared.annotation.coordinate.longitude)
let distance2 = current.distance(from: destination)
let distance1 = round(distance2)
if distance1 >= 1000 {
if distance1 < 10000 {
let distance3 = distance1/1000
let distance = Double(round(10*distance3)/10)
Shared.shared.typeString = "Safehouse · " + String(distance) + " km"
} else {
let distance3 = distance1/1000
let distance = round(distance3)
Shared.shared.typeString = "Safehouse · " + String(distance) + " km"
}
} else {
let distance = self.forTailingZero(temp: distance1)
Shared.shared.typeString = "Safehouse · " + distance + " m"
}
if let name = name {
if let phone = phone {
Shared.shared.nameString = name
Shared.shared.phoneString = phone
let drawerContent = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NewDrawerViewController")
if let drawer = self.parent?.parent as? PulleyViewController {
drawer.setDrawerContentViewController(controller: drawerContent, animated: false)
drawer.setDrawerPosition(position: PulleyPosition.partiallyRevealed, animated: true)
}
}
}
})
}
}
func forTailingZero(temp: Double) -> String{
var tempVar = String(format: "%g", temp)
return tempVar
}
func loop() {
let ref = FIRDatabase.database().reference()
ref.child("safehouses").observeSingleEvent(of: .value, with: { snapshot in
let snapDict = snapshot.value as? NSDictionary
let ii = snapDict?["number"] as? Int
let iii = Int(ii!)
var i = 1
while i <= iii{
let name = String(i)
ref.child("safehouses").child("\(i)").observeSingleEvent(of: .value, with: { snapshot in
let snapDict = snapshot.value as? NSDictionary
let longitude = snapDict?["longitude"]
as? String
let latitude = snapDict?["latitude"] as? String
if let longitude = longitude {
if let latitude = latitude {
let latitude1 = Double(latitude)
if let latitude1 = latitude1 {
let longitude1 = Double(longitude)
if let longitude1 = longitude1 {
self.locs.append(loc(title: name, latitude: latitude1, longitude: longitude1))
self.placeAnnotations()
}
}
}
}
})
i = i + 1
}
})
}
#IBOutlet weak var menuButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
self.revealViewController().rearViewRevealWidth = 175
self.revealViewController().toggleAnimationType = SWRevealToggleAnimationType.easeOut
if self.revealViewController() != nil {
menuButton.target = self.revealViewController()
menuButton.action = #selector(SWRevealViewController.revealToggle(_:))
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
loop()
mapView.delegate = self
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestAlwaysAuthorization()
Shared.shared.mapView = self.mapView
startBackgroundLocationUpdates()
}
}
final class Shared {
static let shared = Shared()
var nameString : String!
var phoneString : String!
var annotation : MKAnnotation!
var mapView : MKMapView!
var typeString : String!
}
First add these property in info.plist
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>description1</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>description2</string>
<key>NSLocationUsageDescription</key>
<string>description3</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>accept to get location</string>
<key>UIBackgroundModes</key>
And then after copy this view controller in your project
import UIKit
import CoreLocation
class ViewController: UIViewController,CLLocationManagerDelegate {
var locationManager:CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
determineMyCurrentLocation()
// Do any additional setup after loading the view.
}
func determineMyCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager!.allowsBackgroundLocationUpdates = true
locationManager!.pausesLocationUpdatesAutomatically = false
let value = locationManager.startMonitoringSignificantLocationChanges()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
print("ankur :- \(region)")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation:CLLocation = locations[0] as CLLocation
print("user latitude = \(userLocation.coordinate.latitude)")
print("user longitude = \(userLocation.coordinate.longitude)")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
{
print("Error \(error)")
}
}
Last step :- Open Capabilities -> In background modes -> click on location updates
If any query comment it
You need to add these keys in info plist :
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
I'm trying to display some shops in my map and it's working fine (the second time the user go to that MapViewController, but the first time (when it's asking for the user permission location) it only displays the user location and the map is not "zoomed" in the user location.
I'm going to show my code, it's very straightforward and simple:
Updated with the new code (it's still not working and the "didChangeAuthorizationStatus" is not printing anything:
import UIKit
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
let LoadURL = "http://www.website.es/shops.json"
var coordinates = CLLocation()
#IBOutlet weak var mapView:MKMapView!
var farmacia = [Farmacia]()
let locationManager = CLLocationManager()
var currentLocation = CLLocation()
var latitudeValor = String()
var longitudeValor = String()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
// Request for a user's authorization for location services
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
requestLocation()
}
}
func requestLocation () {
let status = CLLocationManager.authorizationStatus()
if status == CLAuthorizationStatus.AuthorizedWhenInUse || status == CLAuthorizationStatus.AuthorizedAlways {
self.mapView.showsUserLocation = true
var currentLocation = CLLocation()
print(locationManager.location)
if locationManager.location != nil
{
currentLocation = locationManager.location!
let center = CLLocationCoordinate2D(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
latitudeValor = String(currentLocation.coordinate.latitude)
longitudeValor = String(currentLocation.coordinate.longitude)
self.mapView.setRegion(region, animated: true)
requestPost()
mapView.delegate = self
}
}
}
func locationManager(locationManager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .NotDetermined:
self.locationManager.requestWhenInUseAuthorization()
break
case .AuthorizedWhenInUse:
self.locationManager.startUpdatingLocation()
requestLocation()
break
case .AuthorizedAlways:
self.locationManager.startUpdatingLocation()
requestLocation()
break
case .Restricted:
// restricted by e.g. parental controls. User can't enable Location Services
break
case .Denied:
// user denied your app access to Location Services, but can grant access from Settings.app
break
}
}
/*
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))
self.mapView.setRegion(region, animated: true)
requestPost()
mapView.delegate = self
}
*/
func requestPost () {
let myUrl = NSURL(string: "http://www.website.es/shops_by_position.php");
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "POST"
let postString = "latitude="+latitudeValor+"&longitude="+longitudeValor
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
// JSON RESULTADO ENTERO
//let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)!
//print("responseString = \(responseString)")
if error != nil
{
//print("error=\(error)")
return
}
else
{
self.farmacia = self.parseJsonData(data!)
}
}
task.resume()
}
func parseJsonData(data: NSData) -> [Farmacia] {
let farmacias = [Farmacia]()
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
// Parse JSON data
let jsonProductos = jsonResult?["farmacias"] as! [AnyObject]
//print(jsonProductos)
for jsonProducto in jsonProductos {
let farmacia = Farmacia()
farmacia.id = jsonProducto["id"] as! String
farmacia.nombre = jsonProducto["nombre"] as! String
farmacia.latitude = jsonProducto["latitude"] as! String
farmacia.longitude = jsonProducto["longitude"] as! String
let stringLat = NSString(string: farmacia.latitude)
let stringLon = NSString(string: farmacia.longitude)
let latitude: CLLocationDegrees = stringLat.doubleValue
let longitude: CLLocationDegrees = stringLon.doubleValue
coordinates = CLLocation(latitude: latitude,longitude: longitude)
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(coordinates, completionHandler: { placemarks, error in
if error != nil
{
//print(error)
return
}
else
{
if placemarks != nil && placemarks!.count > 0 {
let placemark = placemarks?[0]
// Add Annotation
let annotation = MKPointAnnotation()
annotation.title = farmacia.nombre
annotation.coordinate = placemark!.location!.coordinate
self.mapView.addAnnotation(annotation)
}
}
})
}
}
catch let parseError {
print(parseError)
}
return farmacias
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "MyPin"
if annotation.isKindOfClass(MKUserLocation) {
return nil
}
let detailButton: UIButton = UIButton(type: UIButtonType.DetailDisclosure)
// Reuse the annotation if possible
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier)
if annotationView == nil
{
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "pin")
annotationView!.canShowCallout = true
annotationView!.image = UIImage(named: "pin.png")
annotationView!.rightCalloutAccessoryView = detailButton
}
else
{
annotationView!.annotation = annotation
}
return annotationView
}
func mapView(mapView: MKMapView, annotationView: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == annotationView.rightCalloutAccessoryView {
performSegueWithIdentifier("PinDetail2", sender: annotationView)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "PinDetail" {
let destinationController = segue.destinationViewController as! FarmaciaDetailViewController
destinationController.titulo_farmacia = (sender as! MKAnnotationView).annotation!.title!
}
if segue.identifier == "PinDetail2" {
let destinationController = segue.destinationViewController as! FarmaciaWebDetailViewController
destinationController.nombre_farmacia = (sender as! MKAnnotationView).annotation!.title!
}
}
#IBAction func cancelToMap(segue:UIStoryboardSegue) {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
My question is: What I have to change in order to show the user location zoomed and my shop entries the first time the app asks for permission and the user choose "Yes"?
It's my first time using the MapKit framework and I'm a little bit lost, so much appreciated if you can show me some light in my case.
1) Change
class MapViewController: UIViewController, MKMapViewDelegate {
to
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
2) Change
func locationManager(locationManager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .NotDetermined:
self.locationManager.requestAlwaysAuthorization()
to
self.locationManager.requestWhenInUseAuthorization()
3) Add NSLocationWhenInUseUsageDescription to Info.plist
EDIT
4) Add the following code to viewDidLoad
locationManager.delegate = self
EDIT 2
5) Add import to header
import CoreLocation
The documentation for requestWhenInUseAuthorization says
When the current authorization status is kCLAuthorizationStatusNotDetermined, this method runs asynchronously and prompts the user to grant permission to the app to use location services.
So in your code, the authorisation is requested and then execution immediately continues, eventually reaching
if status == CLAuthorizationStatus.AuthorizedWhenInUse
which fails, at status is still not determined.
There is the locationManager:didChangeAuthorizationStatus: callback in CLLocationManagerDelegate that is called once the user allows or denies location access.
I would suggest that you move your logic for the .AuthorizedWhenInUse case in a function and call it either from your viewDidLoad method for cases when authorisation is already granted or from the callback if it is not granted yet.