I have been reading apple developer sources on how location updates work and reading tons stack overflow questions on it as well. Im trying to get my users location on my app all the time when the user put the app in the background. The code below is not my code, I found it from another stacker overflow source question and it is the only thing that gets me close to what i need. the code below works and updates me on the users location for 25 minutes only and then just stops, but what i want is it to work all the time as long as the app is in the background :(. The question was answered but I'm not quite understanding how this whole background stuff works. I've heard apple automatically terminates your app after 3 minutes or so, so anyway to bypass that would help. And also i do have my background modes checked as in Location updates and background fetch and even the info.plist that are needed as well which was said was suppose to automatically wake my app once terminated but that doesn't seem to work as well either. If anyone can help me this would be a tremendous help for my new app, it is the last bit of code i need aha, thank you in advance everyone. would also like to do it without adding a silent audio file so my app doesn't get rejected :)
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.allowsBackgroundLocationUpdates = true
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
var timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: "updateLocation", userInfo: nil, repeats: true)
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
if UIApplication.sharedApplication().applicationState == .Active {
} else {
backgroundTaskIdentifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({ () -> Void in
self.backgroundTimer.invalidate()
self.backgroundTimer = NSTimer.scheduledTimerWithTimeInterval( 60.0, target: self, selector: "updateLocation", userInfo: nil, repeats: true)
})
}
}
func updateLocation() {
var timeRemaining = UIApplication.sharedApplication().backgroundTimeRemaining
print(timeRemaining)
if timeRemaining > 60.0 {
print("timeRemaining > 60.0")
}
} else {
if timeRemaining == 0 {
print("timeRemaining = 0") UIApplication.sharedApplication().endBackgroundTask(backgroundTaskIdentifier)
}
backgroundTaskIdentifier2 = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({ () -> Void in
self.backgroundTimer.invalidate()
self.backgroundTimer = NSTimer.scheduledTimerWithTimeInterval( 60.0, target: self, selector: "updateLocation", userInfo: nil, repeats: true)
})
}
}
You cannot (at present) run an app in the background continuously. That's how Apple intends things to work and you'd have to accept that. Once your app is in the background, it can be terminated at any point in time by the core OS.
Related
I am trying to find out if user does not click or scroll or any other type of events in the application for 3 min; or in other words user is not interactive in the application, then the application will sign him out. similar bank application.
I could able to add GestureRecognizer as follows, but I want to catch up all the activities on the UI, is there a way to handle anything like that?
Set up a tap recognizer on collectionView:
In the viewDidLoad add the following:
let collectionViewTap = UITapGestureRecognizer(target: self, action: #selector(collectionViewTap))
collectionView.addGestureRecognizer(collectionViewTap)
Declare this function which will be called when the collectionView is tapped:
func collectionViewTap() {
print("collectionViewTap")
}
I could able to implement the time difference as follows to see timeDifference as well.
func isUserRequiredToLogin(){
let lastActivityDateAndTime = UserDefaults.standard.object(forKey: "lastActivityDateAndTime") as! Date
let currentDate = Date()
let minutes = currentDate.minutes(from: lastActivityDateAndTime)
print(minutes)
}
var timer: Timer?
func resetTimer() {
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(timeIsOur), userInfo: nil, repeats: false)
}
#objc func timeIsOur() {
//logout
//log something
//alerts
}
you can add a Timer to your code and reset it in any event you want. If timer won't be reset - than timeIsOur will be executed and you do whatever you want - (logout or log something)
try to use this https://www.zerotoappstore.com/how-to-detect-user-inactivity-swift.html It works in my production app.
Theres any way to keep app running while user doesn't kill the app? I found that this can be accomplish with RunLoop.current.run() but when hit the app in this like the app completely freeze, I've a Timer that its declared before the RunLoop and seems to be correctly initialized but the timer doesn't proc, heres a simple example
import Foundation
print("start")
let _ = Timer.init(timeInterval: 1, repeats: true) { _ in
print("proc")
}
RunLoop.current.run()
print("end")
Seems that I'm not catching how this works.
Regards
The problem is merely that you don't know how to use a Timer. It isn't enough to init it. You have to schedule it. Instead of calling init..., call Timer.scheduledTimer... and the whole thing will spring to life for you. Then you'll see that in fact your process is running.
I put this (I can't use a block-based Timer because I'm not using Sierra) and it worked fine:
import Foundation
class TimerHolder:NSObject {
var timer : Timer?
func timerFired(_:Timer) { print("fired") }
override init() {
super.init()
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self,
selector: #selector(timerFired),
userInfo: nil, repeats: true)
}
}
print("start")
_ = TimerHolder()
RunLoop.current.run()
print("end")
I have a function that I want to run in the app background mode and be executed at least every minute to check for wifi connection and to make decisions. I am using locationManager delegate so I can run my NSTimer in the background. However the location manager is consuming a lot of battery power. This app is not for apple release. However I am looking for more efficient settings for location manager so it will not be that power hungry or maybe any other good ideas?.
The current settings that I have are ok, but since I enabled automatic pause for location manager, function updates are delayed too much. Before I was using two delegates methods (didEnterRegion and didExitRegion) those were more power-hungry and not accurate. I read tons of available tutorials and checked other related posts on Stack overflow but have not found anything that would help me to solve my problm
Here is what I have in my delegate function:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.timer = NSTimer.scheduledTimerWithTimeInterval(45, target: self, selector: #selector(self.checkNetworkSSID), userInfo: nil, repeats: true)
manager.stopMonitoringSignificantLocationChanges()
manager.stopUpdatingLocation()
}
Here is what I have in my viewDidLoad and AppDelegate
manager = CLLocationManager()
manager?.delegate = self
manager?.requestWhenInUseAuthorization()
manager?.startUpdatingLocation()
manager?.desiredAccuracy = kCLLocationAccuracyThreeKilometers
manager?.pausesLocationUpdatesAutomatically = true
manager?.activityType = CLActivityType.Fitness
For getting Location in Background you need to call this code on your
code and make sure add NSLocationAlwaysUsageDescription inside your info.plist
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.startMonitoringSignificantLocationChanges()
if #available(iOS 9.0, *) {
locationManager.allowsBackgroundLocationUpdates = true
}
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
So in general terms, this is what i want my program to do: music will play, randomly generate a number between 3 and 12, then set a timer according to that number, when the timer is finished stop the music for 1 second, then after that one second, play the music again(and repeat the process, generate random number...)
This is the code I have written to do so, it works great the first run, but the second run it starts calling the functions at a faster rate(not following the timer) and sometimes in a different order(you can test this by looking at the print out)
var randomTimeInteger = Int()
var stopMusicTimer = NSTimer()
var startMusicTimer = NSTimer()
override func viewDidLoad(){
playMusic()
}
//Call to get random interger
func generateRandomInteger(){
print("Inside generate random integer")
let lower = 3
let upper = 12
randomTimeInteger = lower + Int(arc4random_uniform(UInt32(upper - lower + 1)))
print("Random Integer is : \(randomTimeInteger)")
initiateTimer()
}
//Start the timer
func initiateTimer() {
print("Inside initate Timer")
//Set Timer
stopMusicTimer = NSTimer.scheduledTimerWithTimeInterval(Double(randomTimeInteger), target: self, selector: "stopMusic", userInfo: nil, repeats: true)
}
//Stop the music
func stopMusic(){
print("Inside stop music")
GameViewController.backgroundAudio?.stop()
startMusicTimer = NSTimer.scheduledTimerWithTimeInterval(3.0, target: self, selector: "playMusic", userInfo: nil, repeats: true)
}
func playMusic(){
print("Inside play music")
GameViewController.backgroundAudio?.play()
generateRandomInteger()
}
Any idea what the problem is? Or if there is a better way to do so? PLEASE HELP!
You have one timer that tries to stop the music every 3 to 12 seconds. And sometimes you create a timer that tries to start the music every 3 seconds. And sometimes you create more of these timers. So eventually you have lots and lots of timers that try starting and stopping the music at random time.
To stop a repeating timer, call invalidate.
And don't initialise the timers as you do, NSTimer() returns nothing useful. Just declare the variables as NSTimer?
There are really numerous viable approaches.
Starting from the top, I would sketch the design as follows:
func playMusic(completion: (ErrorType?)->()) {
play(randomDuration()) { error in
if error != nil {
completion(error)
return
}
delay(1.0, f: play)
}
}
func randomDuration() -> Double {...}
func play(duration: Double, completion: (ErrorType?)->()) {
let player = Player(...)
player.resume()
delay(duration) {
player.suspend()
completion(nil)
}
}
Function delay(_:f:) is implemented in terms of dispatch_after.
You will probably notice, that playMusic runs indefinitely. This is by your requirements, but in practice you need a way to stop it.
In my application the user can press a button. That in turn leads to a function call which is showed below:
In ViewController.Swift
#IBAction func pickMeUpButton(sender: AnyObject) {
sendPushNotificationController().sendPushNotification("sendRequest",userInfo: defaults.stringForKey("x73")!, userInf23: defaults.stringForKey("x23")! )
locationController.getLocationForShortTime() // --> here i want the timer to finish the 5 seconds before proceeding
activityIndicator.center = self.view.center
activityIndicator.startAnimating()
self.view.addSubview(activityIndicator)
//activityIndicator.stopAnimating()
}
And this is the class function where the call is being made to
In getUserLocation.swift
func initManager(){
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
func getLocationForShortTime(){
initManager()
timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: "stopGettingLocation", userInfo: nil, repeats: false)
}
func stopGettingLocation(){
locationManager.stopUpdatingLocation()
}
So this will make the application get the users location for 5 seconds and then the timer will stop the updates. What i want to do is when the five seconds has elapsed and the location update stops THEN i would like the calling function to proceed to next line.
I though of some solutions using boolean, but it is not a nice solution. Im thinking there might be a better way to do this?
Others have told you what to do, but not why.
You need to adjust your thinking.
With an event-driven device like an iPhone/iPad, you can't stop processing on the main thread for 5 seconds. The UI would lock up, and after a couple of seconds the system would kill your app as being hung.
Instead, what you do is to invoke a block of code (a closure) after a delay.
You could rewrite your function like this:
#IBAction func pickMeUpButton(sender: AnyObject)
{
sendPushNotificationController().sendPushNotification("sendRequest",
userInfo: defaults.stringForKey("x73")!,
userInf23: defaults.stringForKey("x23")! )
initManager()
//Start the activity indicator during the delay
activityIndicator.center = self.view.center
self.view.addSubview(activityIndicator)
activityIndicator.startAnimating()
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, Int64(5.0 * Double(NSEC_PER_SEC))),
dispatch_get_main_queue())
{
//The code in the braces gets run after the delay value
locationManager.stopUpdatingLocation()
activityIndicator.stopAnimating()
}
//dispatch_after returns immediately, so code here will run before
//the delay period passes.
}
That button action code will:
Call initManager to start the location manager running.
Immediately create an activity indicator, add it to the view controller's content view, and start it spinning.
Then, the call to dispatch_after will wait for 5 seconds before running the code in the braces, which will stop the location manger and stop the activity indicator.
For delaying a function-call you can use dispatch_after. It's syntax is a little bit ugly so you can also use this delay function:
/// delays the execution of the passed function
func delay(delay: Double, closure: ()->()) {
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))),
dispatch_get_main_queue(),
closure)
}
// calling directly (locationManager gets captured; which is in this case negligible to consider)
delay(5.0, closure: locationManager.stopUpdatingLocation)
// or indirect through your function
delay(5.0, closure: stopGettingLocation)
Pass the closure to getLocationForShortTime. The one that should be run once the thing is finished. I can't really test the code, but it's probably something like:
class Handler { // <- This is the wrapper class for completion closures
private let run: Void -> Void
init(_ run: Void -> Void) { self.run = run }
}
lazy var locationManager: CLLocationManager! = self.lazyLocationManager()
func lazyLocationManager() -> CLLocationManager {
let _locationManager = CLLocationManager()
_locationManager.delegate = self
_locationManager.desiredAccuracy = kCLLocationAccuracyBest
_locationManager.requestAlwaysAuthorization()
return _locationManager
}
func getLocationQuick(onComplete: Void -> Void) { // <- This takes the closure
locationManager.startUpdatingLocation()
let timer = NSTimer.scheduledTimerWithTimeInterval(
5,
target: self,
selector: "gotLocationQuick:",
userInfo: Handler(onComplete), // <- Here you wrap the completion closure
repeats: false // and pass it to the timer
)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSDefaultRunLoopMode)
}
func gotLocationQuick(timer: NSTimer) {
locationManager.stopUpdatingLocation()
let completionHandler = timer.userInfo as! Handler
completionHandler.run()
}
#IBAction func pickMeUpButton(sender: AnyObject) {
sendPushNotificationController().sendPushNotification(
"sendRequest",
userInfo: defaults.stringForKey("x73")!,
userInf23: defaults.stringForKey("x23")!
)
activityIndicator.center = self.view.center
self.view.addSubview(activityIndicator)
activityIndicator.startAnimating()
locationController.getLocationQuick() { // <- You post the request for
activityIndicator.stopAnimating() // geolocation and indicate the code
} // to be run once it's finished
}