iOS13 make a background processor to update location

121 views Asked by At

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 &amp; Sports. As well as local offers.</string>
    <key>NSLocationUsageDescription</key>
    <string>Delivering location based Fuel Price Updates, Weather, News &amp; 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()
    }

enter image description here

1

There are 1 answers

2
Paulw11 On

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.