I am developing an iOS app and I want to detect when the user connects/disconnects to Wifi even when the app is closed. I did a lot of research, but still didn't find any solutions to this problem.
Can someone point me in the general direction of how to do this?
It is not possible to detect network connection after the application is closed. The process is shut down and your code cannot be executed.
Check iOS application lifecycle for more details
Maybe you should considered option of application, that can run in background. This is of course possible in iOS, you need Capability type:Background Modes. Then you can check if wifi is availible.
For some unspecified time you can attain this by using Background Fetch.
override init() {
super.init()
initializeBackgroundTask()
NotificationCenter.default.addObserver(self, selector: #selector(networkHasChanged(notification:)), name: NSNotification.Name.reachabilityChanged, object: nil)
}
func networkHasChanged(notification : NSNotification) {
if let reachability = notification.object as? Reachability {
// Do whatever you want to do!!!
}
}
func initializeBackgroundTask() {
if bgTask == UIBackgroundTaskInvalid {
bgTask = UIApplication.shared.beginBackgroundTask(withName: "CheckNetworkStatus", expirationHandler: {
self.endBackgroundTask()
})
}
}
func endBackgroundTask() {
if deepLinkString == nil {
if (self.bgTask != UIBackgroundTaskInvalid) {
UIApplication.shared.endBackgroundTask(self.bgTask)
self.bgTask = UIBackgroundTaskInvalid
}
}
}
deinit {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.reachabilityChanged, object: nil)
}
Also try not to initialise background task if not in use.
Related
I am trying to perform the Finite-Length background task in my app. However, as of now my code is not executed before the app is suspended.
I've followed quite a few tutorials that claims the following to be the way, but obviously I'm getting something wrong. Relevant code should be posted below (please just ask for any clarification if I'm missing something):
class Manager {
private var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
init(){
// Add observer able of detecting when app will go to background
NotificationCenter.default.addObserver(self, selector: #selector(self.didEnterBackground(_:)), name: .UIApplicationDidEnterBackground, object: nil)
}
deinit {
// Observers removed when view controller is dismissed / deallocated
NotificationCenter.default.removeObserver(self)
}
// Routine performed when app will resign from active
#objc private func didEnterBackground(_ notification: Notification){
registerBackgroundTask()
// Code that needs to be executed before app is suspended ------
DispatchQueue.global().sync {
self.isBackgrounding = true
self.shutdownSession()
self.isConnected = false
self.isActivated = false
self.activate = false
self.connectionManager.closeConnectionToPeripherals()
}
// -----------------------------------------------------
self.endBackgroundTask()
}
func registerBackgroundTask() {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskInvalid)
}
func endBackgroundTask() {
print("\n\n\nBackground task ended.\n\n\n")
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
}
I am facing a problem where the background task always seem to be ended before executing the code. How do I ensure that the code in my synchronous block gets executed before app is suspended?
I don't quite understand the "registerBackgroundTask()" either - even though the internet insists on implementing it this way - as it calls the endBackgroundTask().
You need to move your self.endBackgroundTask() call into your DispatchQueue block, right after self.connectionManager.closeConnectionToPeripherals().
The registerBackgroundTask method does not directly call self.endBackgroundTask(), instead it is only called if the background task expired. Usually, this happens if your app does not complete the task after ~30s in background.
I need to check if the app is moved to the background. Why?
Well because my app works with bluetooth and only one person can be connected to this device at a time. Therefore if they are not using it and the app is in the background, disconnect them and send them to the connect main page.
Now I have accomplished this. I have a selector in the main first class and a function to disconnect and send to first page. But what I didn't realise is that if the control panel is dragged up, the app is in the 'background'.
From looking around there doesn't seem to be a way to detect if the control panel is brought up. So does anyone have any ideas on how I can do this differently?
Realistically I just want it so if the app is moved to the background for any other reason than the control panel being brought up, disconnect from the device.
Selector:
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: Notification.Name.UIApplicationWillResignActive, object: nil)
Function:
#objc func appMovedToBackground() {
if (ViewController.connectedPeripheral != nil) {
print("App moved to background!")
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "connectView") as! ViewController
self.navigationController?.pushViewController(nextViewController, animated: true)
ViewController.centralManager.cancelPeripheralConnection(ViewController.connectedPeripheral!)
}
else {
print("App moved to background but no device is connected so no further action taken")
}
}
This is not a duplicate of other questions. I know how to check if app is in background state. I just don't want to disconnect when the control panel is brought up...
In Swift:
if UIApplication.shared.applicationState == .background {
// Add code here...
}
In Objective-C:
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
// Add code here...
}
Hope it works!
Have you tried with adding observer to willResignActive in your view controller?
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: .UIApplicationWillResignActive, object: nil)
func willResignActive(_ notification: Notification) {
// code to execute
}
Then you can use this also. After entering in background state , app will be moved to inactive state.
override func viewDidLoad() {
super.viewDidLoad()
let app = UIApplication.shared
//Register for the applicationWillResignActive anywhere in your app.
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.applicationWillResignActive(notification:)), name: NSNotification.Name.UIApplicationWillResignActive, object: app)
}
func applicationWillResignActive(notification: NSNotification) {
}
I have a JSON file populated with strings data in Documents Directory. In user Interface of application there is a UIButton. On button press, a new string appends into the JSON file.
Now I am looking for any iOS Service that helps me to send these strings (from JSON file) to the server using swift. And this service should be totally independent of my code.
The point is when I press a UIButton, the first step is a string is saved to the JSON file then service should take this string and send it to server if Internet is available.
When a string sent successfully, it should be removed from the JSON file.
This service should track after every 30 seconds if there is any string saved into JSON file, then send it to server.
I Googled and found background fetch but It triggers performFetchWithCompletionHandler function automatically and I cannot know when iOS triggers it. I want to trigger this kind of service my self after every 30 seconds.
Review the Background Execution portion of Apple's App Programming Guide for iOS.
UIApplication provides an interface to start and end background tasks with UIBackgroundTaskIdentifier.
In the top level of your AppDelegate, create a class-level task identifier:
var backgroundTask = UIBackgroundTaskInvalid
Now, create your task with the operation you wish to complete, and implement the error case where your task did not complete before it expired:
backgroundTask = application.beginBackgroundTaskWithName("MyBackgroundTask") {
// This expirationHandler is called when your task expired
// Cleanup the task here, remove objects from memory, etc
application.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
}
// Implement the operation of your task as background task
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// Begin your upload and clean up JSON
// NSURLSession, AlamoFire, etc
// On completion, end your task
application.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
}
What I have done is I just uses the approach discussed by JAL above.
these were the three methods which I used
func reinstateBackgroundTask() {
if updateTimer != nil && (backgroundTask == UIBackgroundTaskInvalid) {
registerBackgroundTask()
}
}
func registerBackgroundTask() {
backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler {
[unowned self] in
self.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskInvalid)
}
func endBackgroundTask() {
NSLog("Background task ended.")
UIApplication.sharedApplication().endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
where updateTimer is of type NSTIMER class
The above functions are in my own created class named "syncService"
This class has an initialiser which is
init(){
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.reinstateBackgroundTask), name: UIApplicationDidBecomeActiveNotification, object: nil)
updateTimer = NSTimer.scheduledTimerWithTimeInterval(30.0, target: self, selector: #selector(self.syncAudit), userInfo: nil, repeats: true)
registerBackgroundTask()
}
Then I just called this class and the whole problem is solved.
DispatchQueue.global(qos: .background).async { // sends registration to background queue
}
Please refer NSURLSessionUploadTask might it help you.
Here is the swift 4 version of the answer by JAL
extension UIApplication {
/// Run a block in background after app resigns activity
public func runInBackground(_ closure: #escaping () -> Void, expirationHandler: (() -> Void)? = nil) {
DispatchQueue.main.async {
let taskID: UIBackgroundTaskIdentifier
if let expirationHandler = expirationHandler {
taskID = self.beginBackgroundTask(expirationHandler: expirationHandler)
} else {
taskID = self.beginBackgroundTask(expirationHandler: { })
}
closure()
self.endBackgroundTask(taskID)
}
}
}
Usage Example
UIApplication.shared.runInBackground({
//do task here
}) {
// task after expiration.
}
I want to create an application similar to WhatsApp and Viber (maybe Skype), for iPhone starting from iOS 8.4, using swift 2.2. So I need to send https requests to my server every 30 seconds (I am using `NSURLSession), to show that my application is online and other contacts can see it. There is no problem with it if application is in active state, timer works perfectly and every 30 seconds. But I need application to work also in Inactive, Background, Suspended and Not running (if that even possible) modes, even if iPhone is sleeping. I just need to send little signal, thats all.
I already have this code:
func registerBackgroundTask() {
backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler {
[unowned self] in self.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskInvalid)
}
func endBackgroundTask() {
UIApplication.sharedApplication().endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
func reinstateBackgroundTask() {
if backgroundTask == UIBackgroundTaskInvalid {
registerBackgroundTask()
}
}
func functionToPerformOnBackground() {
requestToServer()
}
This functions are called by timer in AppDelegate.swift like this:
func applicationDidEnterBackground(application: UIApplication) {
//START BACKGROUND TIMER!!!
if !timerForBackgroundRunning {
timerForBackground = NSTimer.scheduledTimerWithTimeInterval(20, target: self, selector: "functionToPerformOnBackground", userInfo: nil, repeats: true)
registerBackgroundTask()
timerForBackgroundRunning = true
}
}
and
func applicationDidBecomeActive(application: UIApplication) {
if timerForBackgroundRunning {
timerForBackground.invalidate()
if backgroundTask != UIBackgroundTaskInvalid {
endBackgroundTask()
}
timerForBackgroundRunning = false
}
}
This code works perfectly, but when iPhone is going to sleep, it stops to send requests. To force code work again I have to go in application again and then press Home Button on iPhone. I have tested Viber and WhatsApp and I can say that Whatsapp is working similar, but Viber knows how to solve this problem.
Can somebody help me, please?
I will be thankful for any help, advice or anything.
P.S. I have already seen this tutorial: http://www.raywenderlich.com/92428/background-modes-ios-swift-tutorial
Is is great but not complete.
This is my current code
func applicationDidBecameActive(notification:NSNotification) {
println("Application is active")
NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleIdentityChange:", name: NSUbiquityIdentityDidChangeNotification, object: nil)
}
func applicationBecameInactive(notification:NSNotification) {
println("Application is inactive")
NSNotificationCenter.defaultCenter().removeObserver(self, name: NSUbiquityIdentityDidChangeNotification, object: nil)
}
func handleIdentityChange(notification:NSNotification) {
println("this is working")
let fileManager = NSFileManager()
if let token = fileManager.ubiquityIdentityToken {
println("New token is \(token)")
}
else {
println("User has logged out of iCloud")
}
}
"Application is active" & "Application is inactive" works properly. There is no problem there.
I could not get it fire "This is working". By logging into different iCloud account or logging out of iCloud account.
I tried on simulator & on actual device.
Please help me fix this or suggest alternative method to achieve same goal(change in iCloud account).
In Swift I have sometimes needed to add #objc in front of a func for NSNotificationCenter to find it.
So instead of
func handleIdentityChange(notification:NSNotification) {
I'd try
#objc func handleIdentityChange(notification:NSNotification) {
Have a look at my comment in this question. I never received a NSUbiquityIdentityDidChangeNotification either. In iOS your app gets killed when you change accounts. In tvOS you can use NSUbiquitousKeyValueStoreAccountChange.