Issue with CLLocationManager and CLLocationManagerDelegate - ios

I'm running Xcode 10 and iOS 12
I'm getting this warning on each of the delegate methods coded in the class extension of my CLLocationManager singleton:
Instance method 'locationManager(:didChangeAuthorization:)' nearly matches optional requirement 'locationManager(:didChangeAuthorization:)' of protocol 'CLLocationManagerDelegate'
Here is the code:
import Foundation
import CoreLocation
public class PhysicalLocationManager: NSObject {
/*--------------------------------------------------------------------------------*/
//MARK: - Create Singleton Shared Instance
/*--------------------------------------------------------------------------------*/
static let sharedInstance: PhysicalLocationManager = {
let instance = PhysicalLocationManager()
return instance
}()
let locationMgr: CLLocationManager
/*--------------------------------------------------------------------------------*/
//MARK: - Initialization
/*--------------------------------------------------------------------------------*/
override init() {
locationMgr = CLLocationManager()
locationMgr.distanceFilter = kCLDistanceFilterNone
locationMgr.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
super.init()
locationMgr.delegate = self
}
func enableBasicLocationServices() {
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
// Request when-in-use authorization initially
locationMgr.requestWhenInUseAuthorization()
break
case .restricted, .denied:
// Disable location features
// TODO: disableMyLocationBasedFeatures()
break
case .authorizedWhenInUse, .authorizedAlways:
// Enable location features
enableWhenInUseFeatures()
break
}
}
func enableWhenInUseFeatures() {
locationMgr.startUpdatingLocation()
if CLLocationManager.locationServicesEnabled() {
locationMgr.requestLocation()
}
}
}
extension PhysicalLocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
print("\(manager)\tCLLocationManager, didChangeAuthorization\n\(status)")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("\(manager)\tCLLocationManager, didUpdateLocations\n\(locations)")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
// locationManager.stopUpdatingLocation()
}
}
Can anyone see what I am doing wrong here?
Thanks,

Because your PhysicalLocationManager class is public, the delegate methods need to be public as well. Simply add public in front of the three delegate methods and the warnings go away.

Related

Open SwiftUI view only if location permission granted

I have a view (say V) in which a user answers a few questions and their location is recorded. However, the answers only make sense with the user's location.
So what I want is that when the user clicks on a button on the parent view, it takes them to V and immediately asks them for the location permission. If they accept, they can continue on to answer the questions, but if they deny, they navigate back to the parent screen.
I know I can navigate back to the parent screen with self.presentation.wrappedValue.dismiss().
But how do I know when the user has accepted or denied the permission since requestWhenInUseAuthorization() is an asynchronous function?
I'm following this tutorial on getting a user's location on iOS with Swift.
Code for my LocationService:
import CoreLocation
protocol LocationServiceDelegate {
func didFetchCurrentLocation(_ location: GeoLocation)
func fetchCurrentLocationFailed(error: Error)
}
class LocationService: NSObject, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
var delegate: LocationServiceDelegate
init(delegate: LocationServiceDelegate) {
self.delegate = delegate
super.init()
self.setupLocationManager()
}
private func setupLocationManager() {
if canUseLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
}
func requestLocation() {
if canUseLocationManager() {
print(CLAuthorizationStatus.self)
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
}
}
func requestPermission() {
locationManager.requestWhenInUseAuthorization()
}
private func canUseLocationManager() -> Bool {
return CLLocationManager.locationServicesEnabled()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(locations)
if let location = locations.last {
let geoLocation = GeoLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
delegate.didFetchCurrentLocation(geoLocation)
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
delegate.fetchCurrentLocationFailed(error: error)
}
deinit {
locationManager.stopUpdatingLocation()
}
}
struct GeoLocation {
var latitude: Double
var longitude: Double
}
CLLocationManagerDelegate has also the following method:
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
}
This method is called every time the authorization status changed. I would also like to recommend you implementing your LocationService as an ObservableObject instead of using delegate approach.

Location Service as a Singleton in Swift, get stuck on "When In Use"

I'm programming an app that needs "Always location" and I decided to use a Singleton to keep tracking active since I need most of the time the location services even in the background.
When I run the application on my iPhone, the console says that the location service is in "When In Use" mode and my protocol don't get the location updates from the LocationManager.
What's wrong with my Singleton (I'm a Swift newbie please be clear in your answers.
Is it a good idea to use a Singleton for Location Services ?
LocationService.swift (UPDATED)
import Foundation
import CoreLocation
protocol LocationServiceDelegate {
func onLocationUpdate(location: CLLocation)
func onLocationDidFailWithError(error: Error)
}
class LocationService: NSObject, CLLocationManagerDelegate {
public static let shared = LocationService()
var delegate: LocationServiceDelegate?
var locationManager: CLLocationManager!
var currentLocation: CLLocation!
private override init() {
super.init()
self.initializeLocationServices()
}
func initializeLocationServices() {
self.locationManager = CLLocationManager()
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestAlwaysAuthorization()
self.locationManager.pausesLocationUpdatesAutomatically = false
self.locationManager.delegate = self
self.locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
print("Location access was restricted.")
case .denied:
print("User denied access to location.")
case .notDetermined:
self.locationManager.requestAlwaysAuthorization()
case .authorizedAlways: fallthrough
case .authorizedWhenInUse:
print("User choosed locatiom when app is in use.")
default:
print("Unhandled error occured.")
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.currentLocation = locations.last!
locationChanged(location: currentLocation)
}
private func locationChanged(location: CLLocation) {
guard let delegate = self.delegate else {
return
}
delegate.onLocationUpdate(location: location)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
self.locationManager.stopUpdatingLocation()
locationFailed(error: error)
}
private func locationFailed(error: Error) {
guard let delegate = self.delegate else {
return
}
delegate.onLocationDidFailWithError(error: error)
}
}
Then I initialize the singleton :
AppDelegate.swift
let locationService = LocationService.shared
Then my View conforms to my protocol :
ViewController.swift
extension ViewController: LocationServiceDelegate {
func onLocationUpdate(location: CLLocation) {
print("Current Location : \(location)")
}
func onLocationDidFailWithError(error: Error) {
print("Error while trying to update device location : \(error)")
}
}
Yes, You can use singleton for your purpose. Few things you can check with your implementation:
locationManager.pausesLocationUpdatesAutomatically = false.
enable background modes for location updates.
Switch to significant location updates when the app moves to background.
Is it a better way to send notifications to all viewControllers to pass the CLLocation object or its better to conform to my protocol in every controllers ?

Permission popup appear multiple time

I have a project in swift 2.When I launch the app first time there are three different type of permissions popup (Push notification, Location, Photo) that appear on the splash screen.I have add the Permission for location and photos in info.plist
The problem is when the app lunched the one(location) popup appear and disappear without any click then other(photos) popup appear and disappear after few seconds without any click.After few seconds the popup appear one by one and now the popup are display on the screen until I click any one option.
I want to display the permission popup only once when user tap on the button.I have searched about it but all the solutions that I found are in latest version of swift. Any suggestion regarding this is appreciated.
import CoreLocation
private var locationManager: CLLocationManager!
private var locationHandler: ((location: CLLocation?,error: NSError?) -> Void)?
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
func requestCurrentLocation(completionHandler:((location: CLLocation?,error: NSError?)->Void)!) {
locationHandler = completionHandler
if #available(iOS 9.0, *) {
locationManager.requestLocation()
} else {
locationManager.startUpdatingLocation()
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let locationHandler = locationHandler, let location = locations.first {
locationHandler(location: location, error: nil)
self.locationHandler = nil
locationManager.stopUpdatingLocation()
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
if let locationHandler = locationHandler {
locationHandler(location: nil, error: error)
self.locationHandler = nil
}
}
The key is CLLocationManager always alive
import CoreLocation
class LocationManager: NSObject {
static let shared = LocationManager()
private lazy var locationManager: CLLocationManager = {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
return manager
}()
private override init() {
super.init()
}
func askForPermission() {
locationManager.requestWhenInUseAuthorization()
}
func requestLocation() {
locationManager.requestLocation()
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// do something
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// do something
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
// do something
}
}

Shared instance of CLLocationManager

I was trying to create simple shared instance of CLLocationManager for my app.
There was no problem creating shared instance:
import UIKit
import CoreLocation
protocol LocationHandlerDelegate: class {
func locationHandlerDidUpdateLocation(location: CLLocation?)
func locationHandlerDidFailWithError(error: NSError)
}
class LocationHandler: NSObject, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
var location: CLLocation?
weak var delegate: LocationHandlerDelegate?
static let sharedInstance = LocationHandler()
override init() {
super.init()
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
if CLLocationManager.authorizationStatus() == .NotDetermined {
locationManager.requestWhenInUseAuthorization()
}
}
// MARK: -
// MARK: - CLLocationManagerDelegate functions
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
delegate?.locationHandlerDidUpdateLocation(locations.last)
self.location = locations.last
locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
delegate?.locationHandlerDidFailWithError(error)
locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .AuthorizedAlways || status == .AuthorizedWhenInUse {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
}
}
}
But for some reason when I call my shared instance from another class:
class TodayViewController: UIViewController, CLLocationManagerDelegate, LocationHandlerDelegate {
super.viewDidLoad() {
LocationHandler.sharedInstance.delegate = self
if #available(iOS 9.0, *) {
LocationHandler.sharedInstance.locationManager.requestLocation()
} else {
LocationHandler.sharedInstance.locationManager.startUpdatingLocation()
}
}
...
// MARK: - LocationHandlerDelegate function
func locationHandlerDidUpdateLocation(location: CLLocation?) {
if let location = location {
print("Current lcoation: \(location)")
}
else {
// ...
}
}
func locationHandlerDidFailWithError(error: NSError) {
print("Error finding lcoation: \(error.localizedDescription)")
}
}
None of CLLocationManagerDelegate functions in my LocationHandler are called.
But if in TodayViewController I write
LocationHandler.sharedInstance.locationManager.delegate = self
instead of
LocationHandler.sharedInstance.delegate = self
and I implement CLLocationManagerDelegate functions in my TodayViewController instead of LocationHandler then those delegate functions are called. So I guess there may be some problem with instancing? Or am I missing something else?

Memory error implementing CLLocationManagerDelegate in a separate class

I try to move CLLocationManagerDelegate implementation to a separate class (file) in order not to clutter ViewController code but get memory error every time EXC_BAD_ACCESS (code=1, address=0xc)
What am I doing wrong here?
Here's my implementation:
class ViewController: UIViewController {
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = LocationManagerDelegate()
// >=iOS8
if (locationManager.respondsToSelector(Selector("requestWhenInUseAuthorization"))) {
locationManager.requestWhenInUseAuthorization()
} else {
locationManager.startUpdatingLocation()
}
}
}
class LocationManagerDelegate: NSObject, CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
// …
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
// …
}
}
Delegates normally are weak so there is no object retaining your delegate and that's the cause of your Bad memory access error.
You should do something similar to this:
class ViewController: UIViewController {
let locationManager = CLLocationManager()
//instantiate and hold a strong reference to the Core Location Manager Delegate
//Normally you don't need this because the delegate is self
let locationManagerDelegate = LocationManagerDelegate()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self.locationManagerDelegate
// >=iOS8
if (locationManager.respondsToSelector(Selector("requestWhenInUseAuthorization"))) {
locationManager.requestWhenInUseAuthorization()
} else {
locationManager.startUpdatingLocation()
}
}
}
class LocationManagerDelegate: NSObject, CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
// …
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
// …
}
}

Resources