I know I am not the only person having this issue,
Basiclly it seems like SwiftUI can't request Location Always even if it is inside the info.plist you instead have to add a call to it later on (which I have done.)
The issue I can see happening is that people won't change it from while app in use to always.
So how could one make a background processor to keep updating it? - I see Apple allows a background task to be ran that's all
my info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Delivering location based Fuel Price Updates, Weather, News & Sports. As well as local offers.</string>
<key>NSLocationUsageDescription</key>
<string>Delivering location based Fuel Price Updates, Weather, News & Sports. As well as local offers.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>location</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIStatusBarHidden</key>
<false/>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
LocationServices.swift
//
// LocationManager.swift
// CoreLocationDemo
//
// Created by Sheikh Bayazid on 7/18/20.
// Copyright © 2020 Sheikh Bayazid. All rights reserved.
//
import Foundation
import CoreLocation
import Combine
import UIKit
import SwiftUI
class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject {
private let manager: CLLocationManager
static var LMlat = 0.0
static var LMlong = 0.0
@Published var lastKnownLocation: CLLocation?
// var getLat: String {
// return "\(lastKnownLocation?.coordinate.latitude)"
// }
// var getLon: String {
// return "\(lastKnownLocation?.coordinate.longitude)"
// }
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .denied{
print("denied")
}
else{
print("athorized")
manager.requestLocation()
}
}
func start() {
//manager.requestAlwaysAuthorization()
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
}
init(manager: CLLocationManager = CLLocationManager()) {
self.manager = manager
super.init()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
func startUpdating() {
self.manager.delegate = self
// self.manager.requestAlwaysAuthorization()
self.manager.requestWhenInUseAuthorization()
self.manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
lastKnownLocation = locations.last
// print(lastKnownLocation!.coordinate.latitude)
self.manager.requestAlwaysAuthorization()
if(lastKnownLocation!.coordinate.latitude != LocationManager.LMlat && lastKnownLocation!.coordinate.longitude != LocationManager.LMlong)
{
print("Last Known Location Is not a match")
LocationManager.LMlat = lastKnownLocation!.coordinate.latitude
LocationManager.LMlong = lastKnownLocation!.coordinate.longitude
updateServerLocation(latitude: LocationManager.LMlat, longitude: LocationManager.LMlong)
}
/* Maybe Use in Future Version
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: lastKnownLocation!.coordinate.latitude, longitude: lastKnownLocation!.coordinate.longitude)
geoCoder.reverseGeocodeLocation(location, completionHandler:
{
placemarks, error -> Void in
// Place details
guard let placeMark = placemarks?.first else { return }
// Location name
if let locationName = placeMark.location {
print(locationName)
}
// Street address
if let street = placeMark.thoroughfare {
print(street)
}
// City
if let city = placeMark.subAdministrativeArea {
print(city)
}
// Zip code
if let zip = placeMark.isoCountryCode {
print(zip)
}
// Country
if let country = placeMark.country {
print(country)
}
})
*/
//showLocation()
}
func updateServerLocation(latitude:Double,longitude:Double)
{
let locationurl = URL(string: "https://EXAMPLE.com/lat=\(latitude)&long=\(longitude)")!
//print(locationurl )
// print("location: \(MusicPlayer.uuid ?? "") lat: \(latitude), long: \(longitude)")
URLSession.shared.dataTask(with: locationurl) { (data, res, err) in
DispatchQueue.main.async{
// print("The Server should of updated")
// guard let data = data else { return }
}
return
}.resume()
}
// func showLocation(){
// print("From showLocation method")
// print("Latitude: \(getLat)")
// print("Longitude: \(getLon)")
// }
}
and in my ContentView.Swift
var lat: String{
return "\(location.lastKnownLocation?.coordinate.latitude ?? 0.0)"
}
var lon: String{
return "\(location.lastKnownLocation?.coordinate.longitude ?? 0.0)"
}
init() {
self.location.startUpdating()
}
You have a few things that I suggest you do to improve your use of location.
Firstly, it seems that you may be getting confused by the fact that when you ask for "always" location permission on iOS 13, the user actually gets prompted for "when in use". On iOS 13.4 you can trigger a prompt for "always" by first requesting (and receiving) "when in use" and then asking for "always". There are cases where this is the right approach, but I don't think your app is one of them.
You should start by watching What's new in Core Location from WWDC 2019. It explains how provisional-always location permission works.
Looking at your code, it seems that you are trying to re-invent significant location change monitoring. Core Location can do this for you, providing an update only when the user has moved 500m or more; this sounds like it would suit your use case.
Also, based on your use case I can't see that you actually need "always" permission on ios13+; you can use "when in use" for significant location change monitoring with background mode enabled. You can stop background location updates when the user stops the audio stream; there is no good reason for your app to have access to the user's location when they aren't playing audio.
Note that in iOS 12 and earlier you will need to ask for "always" permission, but since you mention SwiftUI, presumably iOS 13 is your minimum supported version.
Finally, with regard to Swift UI, an object like
LocationServices
should be created in your scene delegate and injected into the environment, not created by a view.