I have created an iOS application which just sends the Location Coordinates to the server after the user's login. I have also implemented the widget which takes the snapshot of the location displayed it on the widget screen and send it to the server. The application is simple. I just want to open the app or widget for a short time in the background after 20-30 mins and send the location to the server.
For this, I have implemented the Background Fetch in the app. It is working while simulating from the debugger. But won't work on the real iOS device. App Code is below.
If there is any other solution to perform such task let me know except the Push/Remote Notifications. Thanks
AppDelegate.swift
import UIKit
import UserNotifications
import Alamofire
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
var myLat:Double = 0.0
var myLong:Double = 0.0
let locationManager = CLLocationManager()
var MSISDN = ""
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UIApplication.shared.setMinimumBackgroundFetchInterval(
UIApplication.backgroundFetchIntervalMinimum)
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self as? CLLocationManagerDelegate
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
return true
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
myLat = locValue.latitude
myLong = locValue.longitude
print(locValue)
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if let userDefaults = UserDefaults(suiteName: "group.com.demo.spoton") {
var value1 = userDefaults.bool(forKey: "IsLogin")
if value1 == true {
print (value1)
MSISDN = userDefaults.string(forKey: "MSISDN")!
var fetchResult: UIBackgroundFetchResult!
print("i'm executing a task")
let Type = 2
let root = "https://demourl.com/locreciver.php?latitude="
let url = "\(root)\(myLat)&longitude=\(myLong)&type=\(2)&msisdn=\(MSISDN)&battery=\(1)";
Alamofire.request(url, method:.post).responseString {
response in
switch response.result {
case .success:
print(response)
completionHandler(.newData)
case .failure(let error):
print(error)
completionHandler(.failed)
}
}
return
}
else{
print("notLogin")
}
}
}// fecth function
func applicationWillResignActive(_ application: UIApplication) {
}
func applicationDidEnterBackground(_ application: UIApplication) {
}
func applicationWillEnterForeground(_ application: UIApplication) {
}
func applicationDidBecomeActive(_ application: UIApplication) {
}
func applicationWillTerminate(_ application: UIApplication) {
}
}
// TodayViewController.swift //Widget
import UIKit
import NotificationCenter
import CoreLocation
import Alamofire
import Swift
import GoogleMaps
import MapKit
class TodayViewController: UIViewController, NCWidgetProviding, CLLocationManagerDelegate,MKMapViewDelegate {
#IBOutlet weak var lblTitle: UILabel!
#IBOutlet weak var mapImage: UIImageView!
let locationManager = CLLocationManager()
var myLat:Double = 0.0
var myLong:Double = 0.0
var MSISDN = ""
#objc func doLaunchApp(){
if let url = NSURL(string: "mainAppUrl://"){
self.extensionContext?.open(url as URL, completionHandler: nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// self.extensionContext?.widgetLargestAvailableDisplayMode = NCWidgetDisplayMode.expanded
// let url = URL(string: "mainAppUrl://")!
// self.extensionContext?.open(url, completionHandler: { (success) in
// if (!success) {
// print("error: failed to open app from Today Extension")
// }
// })
//
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(TodayViewController.doLaunchApp))
self.view.addGestureRecognizer(tapGesture)
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
myLat = locValue.latitude
myLong = locValue.longitude
}
func SendData(){
print("Sent")
takeShot()
var batteryLevel:Float = UIDevice.current.batteryLevel
let Type = 2
let root = "https://demourl.com/locreciver.php?latitude="
let url = "\(root)\(myLat)&longitude=\(myLong)&type=\(Type)&msisdn=\(MSISDN)&battery=\(batteryLevel)";
Alamofire.request(url, method:.post).responseString {
response in
switch response.result {
case .success:
print(response)
case .failure(let error):
print(error)
}
}
}
func takeShot(){
let mapSnapshotOptions = MKMapSnapshotter.Options()
// Set the region of the map that is rendered.
let location = CLLocationCoordinate2DMake(myLat, myLong) // Apple HQ
let region = MKCoordinateRegion(center: location, latitudinalMeters: 900, longitudinalMeters: 900)
mapSnapshotOptions.region = region
// Set the scale of the image. We'll just use the scale of the current device, which is 2x scale on Retina screens.
mapSnapshotOptions.scale = UIScreen.main.scale
// Set the size of the image output.
mapSnapshotOptions.size = CGSize(width: mapImage.frame.width, height: mapImage.frame.height)
// Show buildings and Points of Interest on the snapshot
mapSnapshotOptions.showsBuildings = true
mapSnapshotOptions.showsPointsOfInterest = true
let snapShotter = MKMapSnapshotter(options: mapSnapshotOptions)
snapShotter.start { (snapshot:MKMapSnapshotter.Snapshot?, NSError) in
let image = snapshot?.image
var annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: self.myLat, longitude: self.myLong)
annotation.title = "Your Title"
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "annotation")
let pinImage = UIImage(named: "pin")
print(pinImage)
UIGraphicsBeginImageContextWithOptions(image!.size, true, image!.scale);
image?.draw(at: CGPoint(x: 0, y: 0)) //map
pinImage!.draw(at: (snapshot?.point(for: annotation.coordinate))!)
annotationView.drawHierarchy(in: CGRect(x: snapshot!.point(for: annotation.coordinate).x, y: (snapshot?.point(for: annotation.coordinate).y)!, width: annotationView.frame.size.width-100, height: annotationView.frame.size.height-100), afterScreenUpdates: true)
let finalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// completion(image: finalImage, error: nil)
self.mapImage.image = finalImage
//self.mapImage.image = snapshot?.image
}
}
func widgetPerformUpdate(completionHandler: (#escaping (NCUpdateResult) -> Void)) {
if let userDefaults = UserDefaults(suiteName: "group.com.demo.spoton") {
var value1 = userDefaults.bool(forKey: "IsLogin")
if value1 == true {
print (value1)
MSISDN = userDefaults.string(forKey: "MSISDN")!
lblTitle.text = "Service Active"
let timer1 = Timer.scheduledTimer(withTimeInterval: 1500.0, repeats: false) { (timer) in
self.SendData()
}
let timer2 = Timer.scheduledTimer(withTimeInterval: 1500.0, repeats: true) { (timer) in
print("Looop")
self.SendData()
}
}
else {
lblTitle.text = "SpotOn Service InActive. Needs Login"
}
}
completionHandler(NCUpdateResult.newData)
}
}
Related
I would like to send the device's location to a server every x minutes, even if the location does not change, while the app is in the background. (most optimal timeframe would be like 1.5 - 2 hours)
Currently it gets terminated after 30 sec.
I have seen some articles about adding these to the AppDelegate's didFinishLaunchingWithOptions method:
application.setMinimumBackgroundFetchInterval(1800)
UIApplication.shared.setMinimumBackgroundFetchInterval(1800)
but it got deprecated in iOS 13.
The AppDelegate looks like this:
import UIKit
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
var backgroundTaskTimer: Timer! = Timer()
var locationManager: LocationManager!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
locationManager = LocationManager()
let coordinator = AppCoordinator(window: window)
coordinator.start()
return true
}
func applicationDidEnterBackground(_ application: UIApplication) {
doBackgroundTask()
}
func applicationWillEnterForeground(_ application: UIApplication) {
if backgroundTaskTimer != nil {
backgroundTaskTimer.invalidate()
backgroundTaskTimer = nil
}
}
#objc func startTracking() {
self.locationManager.sendLocation()
}
func doBackgroundTask() {
DispatchQueue.global(qos: .default).async {
self.beginBackgroundTask()
if self.backgroundTaskTimer != nil {
self.backgroundTaskTimer.invalidate()
self.backgroundTaskTimer = nil
}
//Making the app to run in background forever by calling the API
self.backgroundTaskTimer = Timer.scheduledTimer(timeInterval: AppEnvironment.backgroundTimeInterval,
target: self, selector: #selector(self.startTracking),
userInfo: nil, repeats: true)
RunLoop.current.add(self.backgroundTaskTimer, forMode: RunLoop.Mode.default)
RunLoop.current.run()
// End the background task.
self.endBackgroundTask()
}
}
func beginBackgroundTask() {
backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(withName: "Track trip", expirationHandler: {
self.endBackgroundTask()
})
}
func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(backgroundUpdateTask)
backgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
}
}
The LocationManager looks like this:
import Foundation
import CoreLocation
class LocationManager: NSObject, CLLocationManagerDelegate {
private let locationService: LocationServiceProtocol
private let manager = CLLocationManager()
private var lastLocation: Coordinate?
private var offlineCoordinates: [Coordinate] = []
init(locationService: LocationServiceProtocol = LocationService()) {
self.locationService = locationService
}
func sendLocation() {
manager.delegate = self
manager.requestLocation()
}
private func handleLocation() {
guard let coordinate = lastLocation else { return }
guard Connectivity.isConnectedToTheInternet else {
offlineCoordinates.append(coordinate)
return
}
if !offlineCoordinates.isEmpty {
offlineCoordinates.forEach { postCoordinate($0) }
offlineCoordinates.removeAll()
}
postCoordinate(coordinate)
}
private func postCoordinate(_ coordinate: Coordinate) {
locationService.sendLocation(coordinate) { [weak self] result in
guard self != nil else { return }
switch result {
case .failure(let error):
print(error.localizedDescription)
case .success(_):
print("Location sent.")
}
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
manager.stopUpdatingLocation()
manager.delegate = nil
guard let location = locations.first else { return }
let latitude = location.coordinate.latitude
let longitude = location.coordinate.longitude
print("\tLatitude: \(latitude), Longitude: \(longitude)")
lastLocation = Coordinate(lat: latitude, lon: longitude,
supervisorNfc: GlobalAppStorage.shared.defaults.string(forKey: Constants.StorageKeys.supervisorNfc) ?? "")
handleLocation()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
}
In Background Modes the following are enabled:
• Location updates
• Background fetch
• Background processing
In Info.plist I have set the following properties:
• Privacy - Location Usage Description
• Privacy - Location When In Use Usage Description
• Privacy - Location Always and When In Use Usage Description
• and the above mentioned under the Required background modes.
Thanks in advance for your help!
I'm having trouble with CoreData a little bit, everything work fine separately but when try to run them everything togather a got the error [Pins latitude]: unrecognized selector sent to instance
When I fetch the request in viewDidLoad I can't add any pen to the map .. but if I load the store with out fetching the request I can add pins to the map and they would get stored.
here is my TravalLocationsMap.swift
class TravelLocationsMap : UIViewController, NSFetchedResultsControllerDelegate{
#IBOutlet var PinchGesture: UIPinchGestureRecognizer!
#IBOutlet weak var MapView: MKMapView!
let userDefaults = UserDefaults()
var keysValues = [String : CLLocationDegrees]()
let locationManger = CLLocationManager()
var pinCoordinate = CGPoint()
var operationQueue: [BlockOperation]!
var fetchedResultsController:NSFetchedResultsController<Pins>!
fileprivate func setupFetchedResultsController() {
let fetchRequest:NSFetchRequest<Pins> = Pins.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: AppDelegate.dataController.viewContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("The fetch could not be performed: \(error.localizedDescription)")
}
}
#IBAction func PinchGesture(_ sender: Any) {
guard let guestureView = PinchGesture.view else {
return
}
guestureView.transform = guestureView.transform.scaledBy(x: PinchGesture.scale, y: PinchGesture.scale)
PinchGesture.scale = 1
}
fileprivate func restoreUserDefaultsData() {
switch (userDefaults.value(forKey: "lat") as? CLLocationDegrees,userDefaults.value(forKey: "long") as? CLLocationDegrees , userDefaults.value(forKey: "latDelta") as? CLLocationDegrees , userDefaults.value(forKey: "longDelta") as? CLLocationDegrees) {
case let (latitude?, longitude?,latDelta?,longDelta?):
let location = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
let regionCoordinate = setRegion(location: location, latDelta: latDelta, longDelta: longDelta)
MapView.setRegion(regionCoordinate, animated: true)
break
default:
print("Something Went Wrong Brother!")
break
}
}
fileprivate func populatePinsOnTheMap(){
guard let pins = fetchedResultsController.fetchedObjects else {
return
}
for pin in pins {
let Annotation = MKPointAnnotation()
Annotation.coordinate = CLLocationCoordinate2D(latitude: pin.latitude, longitude: pin.longitude)
self.MapView.addAnnotation(Annotation)
}
}
override func viewDidLoad() {
super.viewDidLoad()
MapView.delegate = self
setupFetchedResultsController()
restoreUserDefaultsData()
populatePinsOnTheMap()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// fetchedResultsController = nil
}
func setupRecognizers() {
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action:#selector(handleLongPress(recognizer:)))
view.addGestureRecognizer(longTapRecognizer)
}
#objc func handleLongPress(recognizer: UIGestureRecognizer) {
if recognizer.state == .began {
pinCoordinate = recognizer.location(in: MapView)
let coordinate = MapView.convert(pinCoordinate, toCoordinateFrom: MapView)
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
self.MapView.addAnnotation(annotation)
addPins(latitude: coordinate.latitude, longitude: coordinate.longitude)
recognizer.state = .ended
}
}
func addPins(latitude:CLLocationDegrees,longitude:CLLocationDegrees){
let pin = Pins(context: AppDelegate.dataController.viewContext)
print("This is Pin instance \(pin)")
pin.latitude = latitude
pin.longitude = longitude
pin.creationDate = Date()
print("This is pin after set the values \(pin)")
try? AppDelegate.dataController.viewContext.save()
}
func setRegion(location : CLLocationCoordinate2D, latDelta:CLLocationDegrees,longDelta:CLLocationDegrees) -> MKCoordinateRegion{
let north = CLLocation(latitude: location.latitude - latDelta * 0.5, longitude: location.longitude)
let south = CLLocation(latitude: location.latitude + latDelta * 0.5, longitude: location.longitude)
let east = CLLocation(latitude: location.latitude, longitude: location.longitude - longDelta * 0.5)
let west = CLLocation(latitude: location.latitude, longitude: location.longitude + longDelta * 0.5)
let northToSouth = north.distance(from: south)
let eastToWest = east.distance(from: west)
return MKCoordinateRegion(center: location, latitudinalMeters: northToSouth, longitudinalMeters: eastToWest)
}
}
extension TravelLocationsMap : MKMapViewDelegate{
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
keysValues = ["long" : MapView.centerCoordinate.longitude , "lat" : MapView.centerCoordinate.latitude , "latDelta" : MapView.region.span.latitudeDelta , "longDelta" : MapView.region.span.longitudeDelta]
userDefaults.setValuesForKeys(keysValues)
setupRecognizers()
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView!.canShowCallout = true
pinView!.pinTintColor = .red
pinView!.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}
else {
pinView!.annotation = annotation
}
return pinView
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
operationQueue = [BlockOperation]()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
let operation = BlockOperation(block: { () -> Void in
let pin = Pins()
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: pin.latitude, longitude: pin.longitude)
self.MapView.addAnnotation(annotation)})
operationQueue.append(operation)
default:
break
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
for operation in operationQueue {
operation.start()
}
operationQueue = nil
}
}
Here is my AppDelegate.swift
import UIKit
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
static var dataController = DataController(modelName: "TravalLocationMap")
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppDelegate.dataController.load()
// Override point for customization after application launch.
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
Any suggestion why this is happing.
Thank you in advance.
I am developing an application to send the current user location every 15 minutes via the MQTT client framework. When the application is in the foreground it works fine but when the application is in background, the MQTT delegate function "messageDelivered" doesn't get called
We want to use the MQTT client framework to publish message in background in iOS swift.
import UIKit
import MQTTClient
class MainViewController: UIViewController {
let MQTT_HOST = "next.nanolink.com" // or IP address e.g. "192.168.0.194"
//let MQTT_HOST = "tnclicks.free.beeceptor.com" // or IP address e.g. "192.168.0.194"
let MQTT_PORT: UInt32 = 1883
private var transport = MQTTCFSocketTransport()
fileprivate var session = MQTTSession()
fileprivate var completion: (()->())?
override func viewDidLoad() {
super.viewDidLoad()
//notification observer
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveData(_:)), name: .didReceiveData, object: nil)
//MQTT
self.session?.delegate = self
self.transport.host = MQTT_HOST
self.transport.port = MQTT_PORT
session?.transport = transport
updateUI(for: self.session?.status ?? .created)
session?.connect() { error in
print("connection completed with status \(String(describing: error?.localizedDescription))")
if error != nil {
self.updateUI(for: self.session?.status ?? .created)
} else {
self.updateUI(for: self.session?.status ?? .error)
}
}
}
private func subscribe() {
self.session?.subscribe(toTopic: "test/message", at: .exactlyOnce) { error, result in
print("subscribe result error \(String(describing: error)) result \(result!)")
}
}
private func updateUI(for clientStatus: MQTTSessionStatus) {
DispatchQueue.main.async {
switch clientStatus {
case .connected:
print("Connected")
self.publishMessage("on", onTopic: "test/message")
case .connecting,
.created:
print ("Trying to connect...")
default:
print ("Connetion Failed...")
}
}
}
private func publishMessage(_ message: String, onTopic topic: String)
{
session?.publishData(message.data(using: .utf8, allowLossyConversion: false), onTopic: topic, retain: false, qos: .exactlyOnce)
}
#objc func onDidReceiveData(_ notification:Notification) {
print("check return")
guard session?.status == .connected else {
self.updateUI(for: self.session?.status ?? .error)
return
}
let obj = notification.object! as! NSMutableDictionary
print(notification.object!)
let notificationLatitude = obj.value(forKey: "latitude")!
let notificationLongitude = obj.value(forKey: "longitude")!
//let notificationLongitude = notification.object
// print(" Saved latitude:", latitude!)
// print(" Saved longitude:", longitude!)
print(" notification latitude:", notificationLatitude)
print(" notification longitude:", notificationLongitude)
guard session?.status == .connected else {
return
}
publishMessage("on", onTopic: "test/message")
}
}
extension MainViewController: MQTTSessionManagerDelegate, MQTTSessionDelegate {
func newMessage(_ session: MQTTSession!, data: Data!, onTopic topic: String!, qos: MQTTQosLevel, retained: Bool, mid: UInt32) {
if let msg = String(data: data, encoding: .utf8) {
print("topic \(topic!), msg \(msg)")
}
}
func messageDelivered(_ session: MQTTSession, msgID msgId: UInt16) {
print("delivered")
DispatchQueue.main.async {
self.completion?()
}
}
}
extension Notification.Name {
static let didReceiveData = Notification.Name("didReceiveData")
}
We have implemented update location in Background so update your code with update location to send Message in background.
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,CLLocationManagerDelegate {
var window: UIWindow?
var locationManager = CLLocationManager()
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
var bgtimer = Timer()
var latitude: Double = 0.0
var longitude: Double = 0.0
var current_time = NSDate().timeIntervalSince1970
var timer = Timer()
var f = 0
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.doBackgroundTask()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
}
func applicationWillEnterForeground(_ application: UIApplication) {
print("Entering foreBackground")
}
func applicationDidBecomeActive(_ application: UIApplication) {
}
func applicationWillTerminate(_ application: UIApplication) {
}
func applicationDidEnterBackground(_ application: UIApplication) {
print("Entering Background")
// self.doBackgroundTask()
}
func doBackgroundTask() {
DispatchQueue.main.async {
self.beginBackgroundUpdateTask()
self.StartupdateLocation()
self.bgtimer = Timer.scheduledTimer(timeInterval:-1, target: self, selector: #selector(AppDelegate.bgtimer(_:)), userInfo: nil, repeats: true)
RunLoop.current.add(self.bgtimer, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
self.endBackgroundUpdateTask()
}
}
func beginBackgroundUpdateTask() {
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
func StartupdateLocation() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestAlwaysAuthorization()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error while requesting new coordinates")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
self.latitude = locValue.latitude
self.longitude = locValue.longitude
f+=1
print("New Coordinates: \(f) ")
print(self.latitude)
print(self.longitude)
}
#objc func bgtimer(_ timer:Timer!){
sleep(2)
/* if UIApplication.shared.applicationState == .active {
timer.invalidate()
}*/
self.updateLocation()
}
func updateLocation() {
self.locationManager.startUpdatingLocation()
self.locationManager.stopUpdatingLocation()
}}
I want to ;
Get User Location Every 5 minutes with background modes in Swift 3
But my codes don't any action. I need to get and send server ,longitude , latitude , and altitude values every 5 minutes
My codes under below.
AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
sleep(2)
BackgroundLocationManager.instance.start()
return true
}
BackgroundLocationManager - Class
import Foundation
import CoreLocation
import UIKit
class BackgroundLocationManager :NSObject, CLLocationManagerDelegate {
static let instance = BackgroundLocationManager()
static let BACKGROUND_TIMER = 15.0 // restart location manager every 150 seconds
static let UPDATE_SERVER_INTERVAL = 60 * 5 // 5 minutes server send
let locationManager = CLLocationManager()
var timer:Timer?
var currentBgTaskId : UIBackgroundTaskIdentifier?
var lastLocationDate : NSDate = NSDate()
private override init(){
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
locationManager.activityType = .other;
locationManager.distanceFilter = kCLDistanceFilterNone;
if #available(iOS 9, *){
locationManager.allowsBackgroundLocationUpdates = true
}
NotificationCenter.default.addObserver(self, selector: #selector(self.applicationEnterBackground), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil)
}
func applicationEnterBackground(){
// FileLogger.log("applicationEnterBackground")
start()
}
func start(){
if(CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedAlways){
if #available(iOS 9, *){
locationManager.requestLocation()
} else {
locationManager.startUpdatingLocation()
}
} else {
locationManager.requestAlwaysAuthorization()
}
}
func restart (){
timer?.invalidate()
timer = nil
start()
}
private func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case CLAuthorizationStatus.restricted: break
//log("Restricted Access to location")
case CLAuthorizationStatus.denied: break
//log("User denied access to location")
case CLAuthorizationStatus.notDetermined: break
//log("Status not determined")
default:
//log("startUpdatintLocation")
if #available(iOS 9, *){
locationManager.requestLocation()
} else {
locationManager.startUpdatingLocation()
}
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if(timer==nil){
// The locations array is sorted in chronologically ascending order, so the
// last element is the most recent
guard locations.last != nil else {return}
beginNewBackgroundTask()
locationManager.stopUpdatingLocation()
let now = NSDate()
if(isItTime(now: now)){
//TODO: Every n minutes do whatever you want with the new location. Like for example sendLocationToServer(location, now:now)
}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
beginNewBackgroundTask()
locationManager.stopUpdatingLocation()
}
func isItTime(now:NSDate) -> Bool {
let timePast = now.timeIntervalSince(lastLocationDate as Date)
let intervalExceeded = Int(timePast) > BackgroundLocationManager.UPDATE_SERVER_INTERVAL
return intervalExceeded;
}
func sendLocationToServer(location:CLLocation, now:NSDate){
//TODO
}
func beginNewBackgroundTask(){
var previousTaskId = currentBgTaskId;
currentBgTaskId = UIApplication.shared.beginBackgroundTask(expirationHandler: {
// FileLogger.log("task expired: ")
})
if let taskId = previousTaskId{
UIApplication.shared.endBackgroundTask(taskId)
previousTaskId = UIBackgroundTaskInvalid
}
timer = Timer.scheduledTimer(timeInterval: BackgroundLocationManager.BACKGROUND_TIMER, target: self, selector: #selector(self.restart),userInfo: nil, repeats: false)
}
}
I have an issue with Core Location, I've followed the setup from
https://stackoverflow.com/a/24696878/6140339 this answer, and placed all my code in AppDelegate, but I can't figure out how to call it, so I created another function that does the same thing as didUpdateLocations, and called it inside my findMyLocation button, and it is only returning nil.
I have tried setting a custom location in Simulator, still nil, I tried using the debugger and setting a location, I even tried testing it on my iphone to see if i could get a location, and still nothing.
is there a way to call didUpdateLocations from my button?
Or am I just doing something else wrong that im missing.
here is my code in my viewController:
import UIKit
import Social
import CoreLocation
class FirstViewController: UIViewController{
//let social = socialFunctions()
let locationManager = CLLocationManager()
let location = locationFunctions()
var locationFixAchieved = false
var currentLocation = CLLocation()
#IBOutlet weak var locationLabel: UILabel!
#IBAction func findMyLocation(sender: AnyObject) {
updateLocation()
print("Location: \(locationManager.location)")
}
#IBAction func postToFacebookButton(sender: UIButton) {
postToFacebook()
}
#IBAction func postTweetButton(sender: UIButton) {
postToTwitter()
}
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .LightContent
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//MARK: - SOCIAL FUNCTIONS
func postToFacebook(){
if(SLComposeViewController.isAvailableForServiceType(SLServiceTypeFacebook)){
let socialController = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
//creates post with pre-desired text
socialController.setInitialText("")
presentViewController(socialController, animated: true, completion: nil)
}
}
func postToTwitter(){
if(SLComposeViewController.isAvailableForServiceType(SLServiceTypeTwitter)){
let socialController = SLComposeViewController(forServiceType: SLServiceTypeTwitter)
//creates post with pre-desired text
socialController.setInitialText("")
presentViewController(socialController, animated: true, completion: nil)
}
}
//MARK: - LOCATION FUNCTIONS
func updateLocation() {
let locations = [CLLocation]()
if (locationFixAchieved == false) {
locationFixAchieved = true
let locationArray = locations as NSArray
let locationObj = locationArray.lastObject as? CLLocation
let coord = locationObj?.coordinate
if coord?.latitude != nil {
locationLabel.text = ("Location \r\n Latitude: \(coord?.latitude) \r\n Longitude: \(coord?.longitude)")
print("Latitude: \(coord?.latitude)")
print("Longitude: \(coord?.longitude)")
} else {
locationLabel.text = ("Could not find location")
print("LAT & LONG are nil")
}
}
}
}
Here is the code i added to my appDelegate
import UIKit
import CoreLocation
let fvc = FirstViewController()
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
var locationManager: CLLocationManager!
var seenError : Bool = false
var locationFixAchieved: Bool = false
var locationStatus : NSString = "Not Started"
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
initLocationManager();
return true
}
func initLocationManager() {
seenError = false
locationFixAchieved = false
locationManager = CLLocationManager()
locationManager.delegate = self
CLLocationManager.locationServicesEnabled()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
locationManager.stopUpdatingLocation()
if (error == true) {
if (seenError == false) {
seenError = true
print(error)
}
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locations = [CLLocation]()
if (locationFixAchieved == false) {
locationFixAchieved = true
let locationArray = locations as NSArray
let locationObj = locationArray.lastObject as? CLLocation
let coord = locationObj?.coordinate
print("Latitude: \(coord?.latitude)")
print("Longitude: \(coord?.longitude)")
//fvc.locationLabel.text = ("Location \r\n Latitude: \(coord?.latitude) \r\n Longitude: \(coord?.longitude)")
}
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
var shouldIAllow = false
switch status {
case CLAuthorizationStatus.Restricted:
locationStatus = "Restricted Access to location"
case CLAuthorizationStatus.Denied:
locationStatus = "User denied access to location"
case CLAuthorizationStatus.NotDetermined:
locationStatus = "Status not determined"
default:
locationStatus = "Allowed location Access"
shouldIAllow = true
}
NSNotificationCenter.defaultCenter().postNotificationName("LabelHasBeenUpdated", object: nil)
if (shouldIAllow == true) {
NSLog("Location Allowed")
//Start location services
locationManager.startUpdatingLocation()
} else {
NSLog("Denied access: \(locationStatus)")
}
}
Following dan's comment(thank you) all I had to do was delete the first line, and it shows the coordinates, i have yet to figure out how to call my function to change the label text, but i will post that when i figure it out. EDIT: posted solution below
I changed
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locations = [CLLocation]()
if (locationFixAchieved == false) {
locationFixAchieved = true
let locationArray = locations as NSArray
let locationObj = locationArray.lastObject as? CLLocation
let coord = locationObj?.coordinate
print("Latitude: \(coord?.latitude)")
print("Longitude: \(coord?.longitude)")
//fvc.locationLabel.text = ("Location \r\n Latitude: \(coord?.latitude) \r\n Longitude: \(coord?.longitude)")
}
}
deleting let locations = [CLLocation]()
This is how i called it when i press the button.
func updateLocation() {
let manager = CLLocationManager()
let locValue : CLLocationCoordinate2D = (manager.location?.coordinate)!;
let long = locValue.longitude
let lat = locValue.latitude
print(long)
print(lat)
locationLabel.text = ("Location \r\nLatitude: \(lat) \r\nLongitude: \(long)")
}