I want get a user's location in app background. It works fine if I use a timer of 9 or less seconds. If I use 10 and more seconds I can't get the user's location...
My AppDelegate.swift:
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
var backgroundTaskIdentifier: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
var myTimer: NSTimer?
var locationManager = CLLocationManager()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
return true
}
func isMultitaskingSupported() -> Bool {
return UIDevice.currentDevice().multitaskingSupported
}
func timerMethod(sender: NSTimer) {
let backgroundTimerRemainig = UIApplication.sharedApplication().backgroundTimeRemaining
self.locationManager.startUpdatingLocation()
if backgroundTimerRemainig == DBL_MAX {
println("test1")
} else {
println("test")
self.locationManager.delegate = self
self.locationManager.startUpdatingLocation()
}
}
func applicationWillResignActive(application: UIApplication) {
}
func applicationDidEnterBackground(application: UIApplication){
if !isMultitaskingSupported() {
return
}
myTimer = NSTimer.scheduledTimerWithTimeInterval(60.0,
target: self,
selector: "timerMethod:",
userInfo: nil,
repeats: true)
backgroundTaskIdentifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler {
() -> Void in
UIApplication.sharedApplication().endBackgroundTask(self.backgroundTaskIdentifier)
}
}
func endBackgroundTask() {
let mainQueue = dispatch_get_main_queue()
dispatch_async(mainQueue) {
[weak self] in
if let timer = self!.myTimer {
timer.invalidate()
self!.myTimer = nil
UIApplication.sharedApplication().endBackgroundTask(self!.backgroundTaskIdentifier)
self!.backgroundTaskIdentifier = UIBackgroundTaskInvalid
}
}
}
func applicationWillEnterForeground(application: UIApplication) {
if backgroundTaskIdentifier != UIBackgroundTaskInvalid {
endBackgroundTask()
}
}
func applicationDidBecomeActive(application: UIApplication) {
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func locationManager(manager: CLLocationManager!, didUpdateToLocation locations: [AnyObject]) {
var latValue = locationManager.location.coordinate.latitude
var lonValue = locationManager.location.coordinate.longitude
}
}
Why can't I get the user's location when I use 10 and more seconds?
Because approximately after 10 seconds after calling didEnterBackground the app gets suspended.
The app is in the background but is not executing code. The system
moves apps to this state automatically and does not notify them before
doing so. While suspended, an app remains in memory but does not
execute any code.
It's the iOS app lifecycle.
If you want to get location updates in background you have two options:
background task - but it only keeps the app running for 3 minutes maximum
Run update locations background mode.
Related
I currently have an iOS app and want to embed a Unity application as a subview within my application. I'v been struggling with this over the last couple months on and off.
I was getting issues with building my application for some time, but now it is building and running properly (almost). The view I would like the Unity application to display within isn't displaying anything.
For reference I am using Unity 2019.1.12f1 and XCode 10.3
I am NOT using Vuforia, its just a very basic Unity app with a model and basic animation.
My AppDelegate file is:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var application: UIApplication?
#objc var currentUnityController: UnityAppController!
var isUnityRunning = false
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.application = application
unity_init(CommandLine.argc, CommandLine.unsafeArgv)
currentUnityController = UnityAppController()
currentUnityController.application(application, didFinishLaunchingWithOptions: launchOptions)
// first call to startUnity will do some init stuff, so just call it here and directly stop it again
startUnity()
stopUnity()
return true
}
func applicationWillResignActive(_ application: UIApplication) {f isUnityRunning {
currentUnityController.applicationWillResignActive(application)
}
}
func applicationDidEnterBackground(_ application: UIApplication) {
if isUnityRunning {
currentUnityController.applicationDidEnterBackground(application)
}
}
func applicationWillEnterForeground(_ application: UIApplication) {
if isUnityRunning {
currentUnityController.applicationWillEnterForeground(application)
}
}
func applicationDidBecomeActive(_ application: UIApplication) {
if isUnityRunning {
currentUnityController.applicationDidBecomeActive(application)
}
}
func applicationWillTerminate(_ application: UIApplication) {
if isUnityRunning {
currentUnityController.applicationWillTerminate(application)
}
}
func startUnity() {
if !isUnityRunning {
isUnityRunning = true
currentUnityController.applicationDidBecomeActive(application!)
}
}
func stopUnity() {
if isUnityRunning {
currentUnityController.applicationWillResignActive(application!)
isUnityRunning = false
}
}
I am then using this snippet of code to display the code Unity in the subview
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.startUnity()
self.unityView = UnityGetGLView()
I am absolutely baffled as to why this is not working. Any help would be much appreciated.
Hope this is enough information. Please let me know if there is anything else you need to know.
Your delegate looks all good, but your ViewController is missing some pieces. Try this code:
import UIKit
class UnityViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.startUnity()
showUnitySubView()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.stopUnity()
}
}
func showUnitySubView() {
if let unityView = UnityGetGLView() {
// insert subview at index 0 ensures unity view is behind current UI view
view?.insertSubview(unityView, at: 0)
unityView.translatesAutoresizingMaskIntoConstraints = false
let views = ["view": unityView]
let w = NSLayoutConstraint.constraints(withVisualFormat: "|-0-[view]-0-|", options: [], metrics: nil, views: views)
let h = NSLayoutConstraint.constraints(withVisualFormat: "V:|-64-[view]-49-|", options: [], metrics: nil, views: views)
view.addConstraints(w + h)
}
}
}
I need to detect user inactivity on all view controllers, by detecting all touches made. My current AppDelegate code is not detecting the touches made on UIButton and other UI controls. How do I detect all touches made, including UIButtons, UILabels and UITextfields? I have looked at many Stack Overflow articles but I cant seem to adapt it to my needs. If this is not possible how do I extend my original TimerUIApplication class to the other views to detect touches made. Thanks in advance.
AppDegegate Code:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate{
var window: UIWindow?
static let ApplicationDidTimoutNotification = "AppTimout"
// The timeout in seconds for when to fire the idle timer.
let timeoutInSeconds: TimeInterval = 5
var idleTimer: Timer?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
application.isIdleTimerDisabled = true
let userDefaults = UserDefaults.standard
let defaultValues = ["promotionIsEnabled_preference" : "YES",
"promotionDuration_preference" : "10"]
userDefaults.register(defaults: defaultValues)
userDefaults.synchronize()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
print ("app started")
self.resetIdleTimer()
idleTimer = Timer.scheduledTimer(timeInterval: timeoutInSeconds, target: self, selector: #selector(AppDelegate.idleTimerExceeded), userInfo: nil, repeats: false)
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touched")
self.resetIdleTimer()
}
// Resent the timer because there was user interaction.
func resetIdleTimer() {
if let idleTimer = idleTimer {
idleTimer.invalidate()
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
self.resetIdleTimer()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
idleTimer = Timer.scheduledTimer(timeInterval: timeoutInSeconds, target: self, selector: #selector(AppDelegate.idleTimerExceeded), userInfo: nil, repeats: false)
}
#objc func idleTimerExceeded() {
NotificationCenter.default.post(name: Notification.Name(rawValue: TimerUIApplication.ApplicationDidTimoutNotification), object: nil)
print ("Inactive User")
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewControlleripad : UIViewController = mainStoryboardIpad.instantiateViewController(withIdentifier: "mainPromo") as UIViewController
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = initialViewControlleripad
self.window?.makeKeyAndVisible()
}
}
Original Code that I used in the AppDelegate.
import UIKit
import Foundation
class TimerUIApplication: UIApplication {
static let ApplicationDidTimoutNotification = "AppTimout"
// The timeout in seconds for when to fire the idle timer.
let timeoutInSeconds: TimeInterval = 5
var idleTimer: Timer?
// Resent the timer because there was user interaction.
func resetIdleTimer() {
if let idleTimer = idleTimer {
idleTimer.invalidate()
}
idleTimer = Timer.scheduledTimer(timeInterval: timeoutInSeconds, target: self, selector: #selector(TimerUIApplication.idleTimerExceeded), userInfo: nil, repeats: false)
}
// If the timer reaches the limit as defined in timeoutInSeconds, post this notification.
#objc func idleTimerExceeded() {
NotificationCenter.default.post(name: Notification.Name(rawValue: TimerUIApplication.ApplicationDidTimoutNotification), object: nil)
print ("out")
}
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
if idleTimer != nil {
self.resetIdleTimer()
}
if let touches = event.allTouches {
for touch in touches {
if touch.phase == UITouchPhase.began {
self.resetIdleTimer()
}
}
}
}
}
Swift 4 - Create a CustomWindow in your AppDelegate.swift which goes over your Storyboard Views, that registers touches, but does not cancel them.
Code:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate{
var topWindow: CustomWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
topWindow = CustomWindow(frame: UIScreen.main.bounds)
topWindow?.rootViewController = UIViewController()
topWindow?.windowLevel = UIWindowLevelNormal + 1
topWindow?.isHidden = false
}
Create a CustomWindow.swift class that recieves your actions and handles them. Code:
import UIKit
class CustomWindow: UIWindow{
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// What you want to do in here.
return false
}
I need to do something if iPhone detect (beacon in background) with UUID: "3E06945C-F54A-4BE4-ABB2-79764DA6AC6F" so I am trying to do this, in AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
application.registerUserNotificationSettings(UIUserNotificationSettings(types: [.sound, .alert], categories: nil))
initBeaconManager()
//Fabric.with([Crashlytics.self])
//Fabric.with([Appsee.self])
return true
}
func applicationWillTerminate(_ application: UIApplication) {
print("Application will terminate")
}
func applicationDidEnterBackground(_ application: UIApplication) {
isInForeground = false
isInBackground = true
extendBackgroundTime()
}
func applicationDidBecomeActive(_ application: UIApplication) {
isInForeground = true
isInBackground = false
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "initCarPlaceNotification"), object: nil)
}
func extendBackgroundTime() {
if backgroundTask != UIBackgroundTaskInvalid {
// Still running
return
}
else {
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "Ranging", expirationHandler: { () in
// Background task expired by iOS
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
});
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { //
if !self.isInForeground {
while true {
Thread.sleep(forTimeInterval: 1)
}
}
}
}
func initBeaconManager() {
let beaconsUUIDs = ["1":"3E06945C-F54A-4BE4-ABB2-79764DA6AC6F"]
locationManager = CLLocationManager()
if (locationManager!.responds(to: #selector(CLLocationManager.requestAlwaysAuthorization))) {
locationManager!.requestWhenInUseAuthorization()
}
locationManager!.delegate = self
locationManager!.pausesLocationUpdatesAutomatically = true
for (key, value) in beaconsUUIDs {
let beaconUUID:UUID = UUID(uuidString: value)!
let beaconRegion = CLBeaconRegion(proximityUUID: beaconUUID, identifier: "reg-\(key)")
locationManager!.startMonitoring(for: beaconRegion)
locationManager!.startRangingBeacons(in: beaconRegion)
}
locationManager!.startUpdatingHeading()
locationManager!.startUpdatingLocation()
}
extension AppDelegate {
func rangeForAllBeacons(_ beacons:[CLBeacon]) {
if beacons[0].proximityUUID == UUID(uuidString: "3E06945C-F54A-4BE4-ABB2-79764DA6AC6F") {
displayNotification("work fine..")
}
}
extension AppDelegate:CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
if !isInForeground {
extendBackgroundTime()
}
print(beacons)
for beacon in beacons {
if beacon.rssi < -20 {
nearbyBeacons.append(beacon)
}
}
if firstRegionDetected == "" {
firstRegionDetected = region.identifier
} else if firstRegionDetected == region.identifier {
rangeForAllBeacons(nearbyBeacons)
nearbyBeacons = []
}
}
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
if isInBackground {
extendBackgroundTime()
}
}
But it doesn't work. On the info.plist I use
Privacy - Location Always Usage Description
and
Privacy - Location When In Use Usage Description
I want to get new location every 1min when my app is in background or killed.
Here is my code :
Location Service:
class LocationService: NSObject, CLLocationManagerDelegate {
static let shared = LocationService()
var locationManager = CLLocationManager()
var significatLocationManager = CLLocationManager()
var currentLocation: CLLocation!
var timer = Timer()
var bgTask = UIBackgroundTaskInvalid
func startUpdatingLocation() {
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
if #available(iOS 9.0, *) {
locationManager.allowsBackgroundLocationUpdates = true
}
locationManager.delegate = self
locationManager.startUpdatingLocation()
}
func stopUpdatingLocation() {
locationManager.stopUpdatingLocation()
timer.invalidate()
}
func restartUpdatingLocation() {
locationManager.stopMonitoringSignificantLocationChanges()
timer.invalidate()
startUpdatingLocation()
}
func startMonitoringLocation() {
locationManager.stopUpdatingLocation()
locationManager.startMonitoringSignificantLocationChanges()
timer.invalidate()
}
func changeLocationAccuracy(){
switch locationManager.desiredAccuracy {
case kCLLocationAccuracyBest:
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
locationManager.distanceFilter = 99999
case kCLLocationAccuracyThreeKilometers:
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
default: break
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
bgTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(self.bgTask)
self.bgTask = UIBackgroundTaskInvalid
})
currentLocation = locations.last!
let interval = currentLocation.timestamp.timeIntervalSinceNow
let accuracy = self.locationManager.desiredAccuracy
if abs(interval) < 60 && accuracy != kCLLocationAccuracyThreeKilometers {
timer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(LocationService.changeLocationAccuracy), userInfo: nil, repeats: false)
changeLocationAccuracy()
}
}
}
App delegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if launchOptions?[UIApplicationLaunchOptionsKey.location] != nil {
LocationService.shared.restartUpdatingLocation()
}
}
func applicationDidEnterBackground(_ application: UIApplication) {
LocationService.shared.restartUpdatingLocation()
}
func applicationWillTerminate(_ application: UIApplication) {
LocationService.shared.startMonitoringLocation()
}
View controller:
override func viewDidLoad() {
LocationService.shared.startUpdatingLocation()
}
But my code is not working, i'm not getting new location after a few minutes when my app is in background. and same when my app is suspended or killed.
Is there any solution for that ? please not that i'm using Swift 3 & xcode 8.1.
Thank's in advance
If you call startUpdatingLocation when your app is already in the background, then you will only receive location updates for a few minutes.
If you call startUpdatingLocation while your app is in the foreground and then your app moves to the background, you will continue to receive location updates indefinitely.
If you call startMonitoringSignificantLocationChanges in the foreground and leave it enabled then you can call startUpdatingLocation in the background and receive updates indefinitely.
If the user terminates your app (by swiping up in the app switcher) then you will no longer receive location updates until the user re-launches your app.
Since your code stops and starts location monitoring when it moves to the background of only get location updates for a few minutes. If you start location monitoring foreground and do nothing when the app moves to the background then you will continue to receive location updates.
I want get user location in app background. Works fine if I use timer 9 or less seconds if I use 10 and more seconds I can't get user location...
my code (AppDelegate.swift):
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
var backgroundTaskIdentifier:UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
var myTimer: NSTimer?
var locationManager = CLLocationManager()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
return true
}
func isMultitaskingSupported()->Bool{
return UIDevice.currentDevice().multitaskingSupported
}
func timerMethod(sender: NSTimer){
let backgroundTimerRemainig = UIApplication.sharedApplication().backgroundTimeRemaining
self.locationManager.startUpdatingLocation()
if backgroundTimerRemainig == DBL_MAX{
println("test1");
}else{
println("test");
self.locationManager.delegate = self
self.locationManager.startUpdatingLocation()
}
}
func applicationWillResignActive(application: UIApplication) {
}
func applicationDidEnterBackground(application: UIApplication){
if isMultitaskingSupported() == false{
return
}
myTimer = NSTimer.scheduledTimerWithTimeInterval(60.0,
target: self,
selector: "timerMethod:",
userInfo: nil,
repeats: true)
backgroundTaskIdentifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler { () -> Void in
UIApplication.sharedApplication().endBackgroundTask(self.backgroundTaskIdentifier)
}
}
func endBackgroundTask(){
let mainQueue = dispatch_get_main_queue()
dispatch_async(mainQueue, {[weak self] in
if let timer = self!.myTimer{
timer.invalidate()
self!.myTimer = nil
UIApplication.sharedApplication().endBackgroundTask(self!.backgroundTaskIdentifier)
self!.backgroundTaskIdentifier = UIBackgroundTaskInvalid
}
})
}
func applicationWillEnterForeground(application: UIApplication) {
if backgroundTaskIdentifier != UIBackgroundTaskInvalid{
endBackgroundTask()
}
}
func applicationDidBecomeActive(application: UIApplication) {
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func locationManager(manager: CLLocationManager!, didUpdateToLocation locations:[AnyObject]) {
var latValue = locationManager.location.coordinate.latitude
var lonValue = locationManager.location.coordinate.longitude
}
}
why I can't get user location when I use 10 and more seconds?