Im currently learning how to code using swift. I created an app that simple displays the users location using map view.
In this code block, when does swift call the function locationManager()? I seem to have never called this function, yet it is running all the logic within it
import UIKit
import MapKit
class ViewController: UIViewController,
CLLocationManagerDelegate{
#IBOutlet weak var map: MKMapView!
#IBOutlet weak var AreaLbl: UILabel!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
print("locations = \(locValue.latitude) \(locValue.longitude)")
let userLocation = locations.last
let viewRegion = MKCoordinateRegion(center: (userLocation?.coordinate)!, latitudinalMeters: 600, longitudinalMeters: 600)
self.map.showsUserLocation = true
self.map.setRegion(viewRegion, animated: true)
}
If there are any resources you recommend for learning swift please let me know!
First, a point of order. In Swift, the name of a function includes parameters. Your function isn't named locationManager(). It's named locationManager(_:didUpdateLocations:) (One unnamed parameter and one parameter named "didUpdateLocations".)
Now to your question.
iOS and Mac OS make pretty common use of the delegate design pattern. In that pattern, one object sets itself up to be another object's "delegate". That basically means that the delegate object agrees to wait by the phone for incoming phonecalls.
When you are an object's delegate, you agree to respond to a set of messages that might be sent to you (or functions that might be called.) You "conform to a protocol" that defines the messages you need to respond to. Some of those messages might be required and some might be optional.
It's not Swift calling your function, it's the OS. Specifically the location manager.
Your viewDidLoad() function includes the line locationManager.delegate = self.
That makes you the location manager's delegate. That means that you conform to the CLLocationManagerDelegate protocol. It tells the location manager that you are ready to accept messages (function calls) from the location manager that are defined by that protocol.
When you do that, and then you tell the location manager to start updating your location with the line locationManager.startUpdatingLocation(), the location manager will start calling your locationManager(_:didUpdateLocations:) function at some future time when it detects a change to the device's location.
That is what is causing your locationManager(_:didUpdateLocations:) function to be called.
Related
I have the following code
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CLLocationManagerDelegate {
var locationManager = CLLocationManager()
var coordinates: String?
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//updates the user's location as the user moves
let location: CLLocation = locations[0] as CLLocation
coordinates = "\(location.coordinate.latitude),\(location.coordinate.longitude)"
print(coordinates!)
}
When I run this on the simulator, the program works fine but it doesn't print the coordinates. When I run it on my iPhone, it prints the coordinates. Is there something in the Xcode settings I need to change to fix this? Also, when I try to use the "coordinates" variable in another function, I get an error saying the compiler is finding nil even though I assigned it a value in the locationManager function, so I'm guessing the two problems are connected. Thanks in advance
You can simulate location on the simulator using "Simulate location" button when running your app:
You can select one of the existing locations or you can create a GPX file (for example not a static single point but a route that will simulate constant changing of location). I would recommend you using https://mapstogpx.com/ to create GPX files.
I have been searching for a solution for two days but could not find anything on web.
I was working on a project and then instantly I was not able to declare anything new or iterate anything older that I wrote. I tried opening new project and start again but it was there again. You can see in the photo that it does need declaration for already declared variables such as cool and hype.
Now I am not able to work on any project.
import UIKit
class ViewController: UIViewController, CLLocationManagerDelegate {
var cool: Int = 2
var hype: Int = 2
hype = 2 + cool
override func viewDidLoad() {
super.viewDidLoad()
// Set up the location manager here.
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
// ...
}
Use of undeclared type 'CLLocationManageDelegate'
This is because you haven't imported the CoreLocation module
Add the line import CoreLocation above the import UIKit
You can't perform the assignment of hype in that scope, try moving it into viewDidLoad
You must declare a location manager variable and initialize one:
Add the line var locationManager = CLLocationManager() above your other vars.
import CoreLocation
import UIKit
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
var cool: Int = 2
var hype: Int = 2
override func viewDidLoad() {
super.viewDidLoad()
hype = 2 + cool
// Set up the location manager here.
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
// ...
}
Actually, all your errors are totally generic, caused by syntax and other mistakes.
It should be CLLocationManager needs to be imported.
This produces errors in declaration and other places depending on it.
Variables can be declared but not manipulated outside a function on the top level of a class.
The variable locationManager has not been defined previously
My app seems to work fine on each use except the first one.
I ask for user authorization and I have the appropriate keys in the plist but the rest of viewDidLoad after the lines requesting authorization don't execute. I have attached the breakpoints below and breakpoint 2 isn't hit on the first time the app is used.
I'm pretty sure after authorization is given it just jumps to the func locationManager in the extension.
I could wait till the very end to ask for authorization until everything else is set but not sure if this is the best, or only way, out.
Thanks,
class MapController: UIViewController, GMSMapViewDelegate {
var locationManager = CLLocationManager()
var currentLocation: CLLocation?
#IBOutlet var mapView: GMSMapView!
override func viewDidLoad(){
super.viewDidLoad()
locationManager = CLLocationManager()
--------------------------> breakpoint 1
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
-------------------------> breakpoint 2
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 50
locationManager.startUpdatingLocation()
locationManager.delegate = self
guard let lat = locationManager.location?.coordinate.latitude else {return}
guard let lng = locationManager.location?.coordinate.longitude else {return}
mapView.settings.compassButton = true
mapView.settings.myLocationButton = true
mapView.isMyLocationEnabled = true
let camera = GMSCameraPosition.camera(withLatitude: lat, longitude: lng, zoom: 1)
mapView.camera = camera
mapView.delegate = self
getData()
}
extension MapController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location: CLLocation = locations.last else {return}
let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: 1)
mapView.animate(to: camera)
}
}
Why do you request authorisation to two different things? If you request and get always authorisation, you don't need to request when in use authorisation, since this is only a subset of always authorisation.
Also, these are both asynchronous functions, so you cannot execute location based code right after them, since if you don't have authorisation yet, the code right after requestAuthorization() will be executed before you actually got authorisation and hence the functions won't be called, since you don't have authorisation yet.
You have to check the authorisation status before calling any location related code, such as locationManager.startUpdatingLocation() and only execute the location related code if the status is authorised. If it is not authorised, you have to implement CLLocationManagerDelegate's locationManager(_:didChangeAuthorization:) function and call the location related call inside that function after checking that the result of the change is an authorised status.
What I want to do is have a button that the user can click that would save their current location. Then, on a map, a pin would appear where the save location is. How would I go about doing this? I've searched for some sample code but I can't find any that work or are in Swift.
I have it so the user can also see where they are at at all times. Bellow is what I currently have for code.
import UIKit
import CoreLocation
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate {
var locationManager = CLLocationManager()
#IBOutlet var Map: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!) {
println("Updating Location \(newLocation.coordinate.latitude) , \(newLocation.coordinate.longitude) ")
let span = MKCoordinateSpanMake(0.0009, 0.0009)
let region = MKCoordinateRegion(center: newLocation.coordinate, span: span)
Map.setRegion(region, animated: false)
}
}
What would I add to this code to accomplish my goal that I described in my first paragraph?
Try like this:
Create your button action to findUserLocationAndDropPin():
Capture the userLocationCoordinates using
CLLocationCoordinate2DMake();
Create your pinForUserLocation using MKPointAnnotation();
Assign your pinForUserLocation.coordinate to be equal to
userLocationCoordinates;
Get your mapView and addAnnotation;
Finally ask mapView to showAnnotations.
This code is what I mean and should do that, at least until iOS 8.4 :
#IBAction func findUserLocationAndDropPin(sender: UIButton) {
var userLocationCoordinates = CLLocationCoordinate2DMake(locationManager.location.coordinate.latitude, locationManager.location.coordinate.longitude)
var pinForUserLocation = MKPointAnnotation()
pinForUserLocation.coordinate = userLocationCoordinates
mapView.addAnnotation(pinForUserLocation)
mapView.showAnnotations([pinForUserLocation], animated: true)
}
Good question!
Try adding an array that stores newLocation.coordinate when an IBAction takes place. Then, you can set a pin by using this sample code as an example:
override func viewDidLoad() {
super.viewDidLoad()
// Set map view delegate with controller
self.mapView.delegate = self
let newYorkLocation = CLLocationCoordinate2DMake(40.730872, -74.003066)
// Drop a pin
let dropPin = MKPointAnnotation()
dropPin.coordinate = newYorkLocation
dropPin.title = "New York City"
mapView.addAnnotation(dropPin)
}
But then just set dropPin.coordinate to be the value stored in the array.
Ok, so firstly you want to get permission from the user:
Add the CoreLocation framework
Update info.plist by adding NSLocationWhenInUseUsageDescription
Your code in viewDidLoad is correct
You can use the simpler method:
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) { }
Inside that, set:
var userLocation : CLLocation = locations[0] as! CLLocation
Now create a CLLocationCoordinate2D by using userLocation.latitude and .longitude
create an annotation with MKPointAnnotation(), and set annotation.coordinate to be the CLLocationCoordinate2D above.
set the .title if required
map.addAnnotation(annotation)
This will in effect add a pin every time the user's location is updated - so you might want to think about removing the previous pins. Also, it's not very effective if y you're getting updates every half second or so, so consider simply setting the mapView to show the region every time it is updated, rather than a pin.
Hope this helps!
This is sample code. NSLocationAlwaysUsageDescription key in Info.plist installed. All work fine - program receives the coordinates.... displays them.....
And it continues to update them permanently! In the case of the iOS simulator - it is not critical, but in the case of a real application it is very quickly drain the battery. How to make an application launched Core Location, received location, and got shut down Core Location?
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet var locLabel: UILabel
#IBOutlet var latLabel: UILabel
#IBOutlet var lonLabel: UILabel
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func locButton(sender: AnyObject) {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(manager:CLLocationManager, didUpdateLocations locations:AnyObject[]) {
println("locations = \(locationManager)")
var latValue = locationManager.location.coordinate.latitude
var lonValue = locationManager.location.coordinate.longitude
latLabel.text = String(latValue)
lonLabel.text = String(lonValue)
locLabel.text = "success"
}
}
If you are not much concern about high level of accuracy then you should consider startMonitoringSignificantLocationChanges instead of startUpdatingLocation. It will really make a big difference in battery draining.
This method is more appropriate for the majority of applications that just need an initial user location fix and need updates only when the user moves a significant distance. This interface delivers new events only when it detects changes to the device’s associated cell towers, resulting in less frequent updates and significantly lower power usage.
For more detail you can take a look over CLLocationManager Guide