Using delegate with a static function in Swift - swift

This is a function that I use it in my app, it works perfectly.
class ResenhaEquideosMenuController: UIViewController, CLLocationManagerDelegate {
static let locationManager = CLLocationManager()
func getLocation() {
let status = CLLocationManager.authorizationStatus()
switch status {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
return
case .denied, .restricted:
let alert = UIAlertController(title: "Serviços de localização desativados", message: "Por favor, ative os Serviços de Localização nas Configurações do seu dispositivo", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
return
case .authorizedAlways, .authorizedWhenInUse:
break
}
locationManager.delegate = self
locationManager.startUpdatingLocation() // função didUpdateLocations controla quando GPS capta atualização do Sensor
}
}
But I wanna change this function to a static function just like
static func getLocation() { // some code }
But I got en error on this part of the code
locationManager.delegate = self
Cannot assign value of type 'ResenhaEquideosMenuController.Type' to type 'CLLocationManagerDelegate?'
How can I fix that?

Static functions don't depend on any particular instance of the type that they belong to, so referencing self from inside one as you're doing:
locationManager.delegate = self
doesn't make any sense. self represents a particular object that provides context for the function call, and that's not available to a static function.
How can I fix that?
You're going to have to reconsider your reason for wanting to make getLocation static, and find a different approach.

I didn't find a way to use the delegate with a static function to fix my problem with the CCLocationManager class.
So, based in #DuncanC comment, I create a new class that extends a UIViewController called LocationViewController.
import Foundation
import CoreLocation
class LocationViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
print("___ LocationViewController viewDidLoad")
getLocation()
}
func getLocation() {
let status = CLLocationManager.authorizationStatus()
switch status {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
return
case .denied, .restricted:
let alert = UIAlertController(title: "Serviçoõs de localização desativados", message: "Por favor, ative os Serviços de Localização nas Configurações do seu dispositivo", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
return
case .authorizedAlways, .authorizedWhenInUse:
break
#unknown default:
print("erro desconhecido")
}
locationManager.delegate = self
locationManager.startUpdatingLocation() // função didUpdateLocations controla quando GPS capta atualização do Sensor
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let currentLocation = locations.last {
manager.distanceFilter = 50 // distance changes you want to be informed about (in meters)
manager.desiredAccuracy = 10 // biggest approximation you tolerate (in meters)
//manager.activityType = .automotiveNavigation // .automotiveNavigation will stop the updates when the device is not moving
saveLocationPosition(currentLocation)
}
}
fileprivate func saveLocationPosition(_ currentLocation: CLLocation) {
UserDefaults.standard.set(currentLocation.coordinate.latitude, forKey: RESconstantes.LATITUDE_USER)
UserDefaults.standard.set(currentLocation.coordinate.longitude, forKey: RESconstantes.LONGITUDE_USER)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("locationManager error")
print(error)
}
}
When I want to capture the location of the GPS sensor, I just extend a UIViewController using the LocateViewController.
Just like that example below:
class ResenhaEquideosMenuController: LocationViewController { ...code }
It works for me.

Related

Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee84eaf60) for my CLLocationManager()

I have no idea why this started or how to solve it. The error: Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee84eaf60) occurs on my line:
let locationManager = CLLocationManager()
I declared it in my class above viewDidLoad(). I've run this simulator a ton of times without this problem, and it randomly popped up now. I checked to make sure it wasn't the most recent code I added by running it again without said code, and the error persists. Just in case you think of this, it's not that my default location is set to none, I've had that error before and fixed it. Here are all the parts of my code where my locationManager is used:
let locationManager = CLLocationManager()
then:
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
mapView.delegate = self
mapView.showsUserLocation = true
tapToAddJumpSpotLabel.isHidden = true
}
then:
#IBAction func allowLocationButtonPressed(_ sender: UIBarButtonItem) {
switch CLLocationManager.authorizationStatus() {
// Case when authorization not determined.
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
// Case when authorization granted.
case .authorizedAlways, .authorizedWhenInUse:
let alertController = UIAlertController(title: "Location Access Granted", message: "We already have access to your location. If you want to change this, go to your settings app, and change our location access.", preferredStyle: .alert)
let okayAction = UIAlertAction(title: "Okay", style: .cancel, handler: nil)
alertController.addAction(okayAction)
self.present(alertController, animated: true, completion: nil)
// Case when authorization denied or restricted.
case .restricted, .denied:
let alertController = UIAlertController(title: "Location Access Disabled", message: "We need access to your location in order to provide you with cliff jumping spots.", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let openAction = UIAlertAction(title: "Open Settings", style: .default) { (action) in
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
alertController.addAction(cancelAction)
alertController.addAction(openAction)
self.present(alertController, animated: true, completion: nil)
// Default (Apple has possible future cases to be added to CLAuthorizationStatus).
#unknown default:
return
}
}
finally:
//MARK: - CLLocationManagerDelegate Methods
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
if let error = error as? CLError, error.code == .denied {
locationManager.stopUpdatingLocation()
print(error)
return
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let lastLocation = locations.last!
mapView.centerCoordinate.latitude = lastLocation.coordinate.latitude
mapView.centerCoordinate.longitude = lastLocation.coordinate.longitude
}
}
I have no idea what is causing this so any help is appreciated :)
Well, after some further digging I feel stupid. Simply an old object initialization of one class in another, and vice versa, causing an infinite loop. Good thing is one of them was old and I just forgot to delete it, so there ya go!

Xcode Swift, Geo Location won't show where i am

any one understand why Geo location won't show me where i am, also the permissions don't work over..... I'm baffled... laptop location is fine, also the app permissions are also set, also the Allow permissions tap, box for the user won't appear either
(iv explained this all above and it won't post unless i type more stuff in the box, but could some one explain why this code doesn't work norrr does it display no errors at all, i had also checked - DEBUG - locations... still nothing
import UIKit
import MapKit
import CoreLocation
class MapScreen: UIViewController {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
let regionInMeters: Double = 10000
override func viewDidLoad() {
super.viewDidLoad()
checkLocationServices()
}
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func centerViewOnUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
mapView.setRegion(region, animated: true)
}
}
func checkLocationServices() {
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
checkLocationAuthorization()
} else {
// Show alert letting the user know they have to turn this on.
}
}
func checkLocationAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
mapView.showsUserLocation = true
centerViewOnUserLocation()
locationManager.startUpdatingLocation()
break
case .denied:
// Show alert instructing them how to turn on permissions
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
// Show an alert letting them know what's up
break
case .authorizedAlways:
break
}
}
}
extension MapScreen: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
let region = MKCoordinateRegion.init(center: location.coordinate, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
mapView.setRegion(region, animated: true)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
checkLocationAuthorization()
}
}
Did you add the permission strings to your info.plist?
You need to add explanations for these otherwise the permission prompt won't appear, and no location updates will be send to your app.
NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription
You should also get an error for this in your console so check that as well.

Reevaluate CLLocationManager.authorizationStatus in running app after app location settings change

Thanks to Thomas's advice below I've modified my code from the original request. I'm wondering if I'm trying to do something impossible or, if not, how I could accomplish this.
If the user hasn't authorized location services, I am prompting them via an alert with an "Open Settings" button to change the app's location settings. This works. But upon return from settings to app, I'd like to recognize if the change was made and activate location services. Can this be done? The closure below successfully gets the user to the app's settings, the user can make changes, and the user can return, but the closure fires when the user presses "Open Settings", which is before settings have been changed. BTW: If there's a better way to handle nudging the user to approve an app's currently unauthorized location prefs, I'd appreciate advice. Thanks!
locationManager.requestWhenInUseAuthorization() // request authorization
let authStatus = CLLocationManager.authorizationStatus()
switch authStatus {
case .denied:
let alertController = UIAlertController(title: "Background Location Access Disabled", message: "In order to show the location weather forecast, please open this app's settings and set location access to 'While Using'.", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alertController.addAction(UIAlertAction(title: "Open Settings", style: .`default`, handler: { action in
if #available(iOS 10.0, *) {
let settingsURL = URL(string: UIApplicationOpenSettingsURLString)!
UIApplication.shared.open(settingsURL, options: [:], completionHandler: {(success) in
print("*** Success closure fires")
let newAuthStatus = CLLocationManager.authorizationStatus()
switch newAuthStatus {
case .authorizedWhenInUse:
self.locationManager.startUpdatingLocation()
default:
print("Not .authorizedWhenInUse")
}
})
} else {
if let url = NSURL(string:UIApplicationOpenSettingsURLString) {
UIApplication.shared.openURL(url as URL)
}
}
}))
self.present(alertController, animated: true, completion: nil)
case .authorizedWhenInUse, .authorizedAlways:
locationManager.startUpdatingLocation()
case .restricted :
print("App is restricted, likely via parental controls.")
default:
print("UH!! WHAT OTHER CASES ARE THERE? ")
}
I think the extension code here accomplishes what I want - a simple get location, but which handles all authorization status cases:
- .notDetermined: requestWhenInUseAuthorization
- .authorized: startUpdatingLocations
- .denied: Prompt user with an alert that uses UIApplicationOpenSettingsURLString to open the app's Privacy / Location settings so they can make the change. A return to the app with new status is picked up in didUpdateLocations so user location is captured after updating settings
- .restricted - an alert is shown prompting user to check with parent or system administrator to lift app restrictions.
Alert also shows w/error code if didFailWithError.
Just set up instance variables for locationManager & currentLocation
let locationManager = CLLocationManager()
var currentLocation: CLLocation!
and in viewDidLoad set the delegate & call getLocation()
locationManager.delegate = self
getLocation()
Hopefully this is sound, but recs on better ways to do this are most welcome. Hope it helps someone, as I struggled to find something that was in Swift 3 & comprehensive. Thanks again, Thomas!
extension ViewController: CLLocationManagerDelegate {
func getLocation() {
let status = CLLocationManager.authorizationStatus()
handleLocationAuthorizationStatus(status: status)
}
func handleLocationAuthorizationStatus(status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .authorizedWhenInUse, .authorizedAlways:
locationManager.startUpdatingLocation()
case .denied:
print("I'm sorry - I can't show location. User has not authorized it")
statusDeniedAlert()
case .restricted:
showAlert(title: "Access to Location Services is Restricted", message: "Parental Controls or a system administrator may be limiting your access to location services. Ask them to.")
}
}
func showAlert(title: String, message: String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
func statusDeniedAlert() {
let alertController = UIAlertController(title: "Background Location Access Disabled", message: "In order to show the location weather forecast, please open this app's settings and set location access to 'While Using'.", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alertController.addAction(UIAlertAction(title: "Open Settings", style: .`default`, handler: { action in
if #available(iOS 10.0, *) {
let settingsURL = URL(string: UIApplicationOpenSettingsURLString)!
UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil)
} else {
if let url = NSURL(string:UIApplicationOpenSettingsURLString) {
UIApplication.shared.openURL(url as URL)
}
}
}))
self.present(alertController, animated: true, completion: nil)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
handleLocationAuthorizationStatus(status: status)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let currentLocation = locations.last {
print("My coordinates are: \(currentLocation.coordinate.latitude), \(currentLocation.coordinate.longitude)")
locationManager.stopUpdatingLocation()
updateUserInterface()
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
showAlert(title: "Location Access Failure", message: "App could not access locations. Loation services may be unavailable or are turned off. Error code: \(error)")
}
}

Swift - Core location request permission if not granted

Is there any way to ask users to give permission for location detection, if they denied when they were first asked?
For getting user's current location you need to declare:
let locationManager = CLLocationManager()
In viewDidLoad():
// Ask for Authorisation from the User.
self.locationManager.requestAlwaysAuthorization()
// For use in foreground
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
Then in CLLocationManagerDelegate method you can get user's current location coordinates:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
var locValue:CLLocationCoordinate2D = manager.location.coordinate
print("locations = \(locValue.latitude) \(locValue.longitude)")
}
If user has denied the location then give him option to change location permission from settings:
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("error::: \(error)")
locationManager.stopUpdatingLocation()
let alert = UIAlertController(title: "Settings", message: "Allow location from settings", preferredStyle: UIAlertControllerStyle.Alert)
self.presentViewController(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: TextMessages().callAlertTitle, style: .Default, handler: { action in
switch action.style{
case .Default: UIApplication.sharedApplication().openURL(NSURL(string: UIApplicationOpenSettingsURLString)!)
case .Cancel: print("cancel")
case .Destructive: print("destructive")
}
}))
}
NOTE:
Make sure to add the Privacy key (NSLocationAlwaysAndWhenInUseUsageDescription) to Info.plist file.
No, you can just remind them inside app within an alert, like "For better usage, we will suggest you to give us the permissions to use your location". And to add button "Go to settings" that will redirect the user to the Location Permissions for your application.

iOS Swift 3: Location Privacy Denied

What if the location Privacy Access for an app is Denied?
Evening guys, I'm coding a simple App that uses location when in use.
The Design Pattern
Before everything, when you launch the App, it will check if there is a permission already set.
If not, it shows an alert asking for permission.
If yes and granted, it proceeds doing its job.
If yes and denied, it shows an alert asking to grant access with a button pointing to settings.
it should be possible to return back from the settings to the app.
The apps does its job.
If the user change the privacy settings when the app is still open, the app should be notified, and repeat number 1.
The Code so Far
MainController
let manager = LocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// ...
if manager.getPermission() == false {
//show alert
showAcessDeniedAlert()
}
manager.onLocationFix = {
//This is a function used for a closure
}
}
}
func showAcessDeniedAlert() {
let alertController = UIAlertController(title: "Location Accees Requested",
message: "The location permission was not authorized. Please enable it in Settings to continue.",
preferredStyle: .alert)
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (alertAction) in
// THIS IS WHERE THE MAGIC HAPPENS!!!!
if let appSettings = URL(string: UIApplicationOpenSettingsURLString) {
UIApplication.shared.open(appSettings as URL)
}
}
alertController.addAction(settingsAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
present(alertController, animated: true, completion: nil)
}
LocationManager
import CoreLocation
extension Coordinate {
init(location: CLLocation) {
latitude = location.coordinate.latitude
longitude = location.coordinate.longitude
}
}
final class LocationManager: NSObject, CLLocationManagerDelegate {
let manager = CLLocationManager()
var onLocationFix: ((Coordinate) -> Void)?
override init() {
super.init()
manager.delegate = self
manager.requestLocation()
}
func getPermission() -> Bool {
switch CLLocationManager.authorizationStatus() {
case .authorizedAlways:
return true
case .authorizedWhenInUse:
return true
case .denied:
return false
case .restricted:
return false
case .notDetermined:
manager.requestWhenInUseAuthorization()
return getPermission()
}
}
//MARK: CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
manager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
let coordinate = Coordinate(location: location)
if let onLocationFix = onLocationFix {
onLocationFix(coordinate)
}
}
}
How Can I?
How can I show The AlertController if the privacy is Denied?
With this setup I'm having this error: Warning: Attempt to present <UIAlertController: 0x145ae7ee0> on <xxx.xxController: 0x143e04720> whose view is not in the window hierarchy!.
How can I code the setting button pointing to Settings?
How can I code: "from settings page, I could return to the app"?
viewDidLoad is called after first access self.view property. This mean that for first is called viewDidLoad after this self.view is added on window hierarchy. Move checking code to viewDidAppear(_:) function and be sure that your view controller is presented.
Code that open Settings app seems to be ok, but don't forget to check from which system version it's available.
Your app is not able to interact somehow with other apps while it's in background state.