i want to know if it is possible to launch a background process in iOS. In my background process every 30 minutes there should a function get called, in order to check in my database if there are new messages, etc in order to send a notification. I don't want to implement push notifications.
I don't know where to implement this feature, and if there are override functions e.g. in the AppDelegate.
Thanks for your help,
hannes
What you want is called "Background Fetch" and is available starting from iOS 7:
https://www.objc.io/issues/5-ios7/multitasking/
Yes it is possible to launch a background process in iOS. It is also possible to launch this process every 30 minutes. You need to setup two things! You need to send every 30 minutes a silent Push with your server.(e.g. with a Cloud Code job in Parse(Warning! Since Parse will be shutdowned in January 2017, it is not clever working on this platform)
This silent push initiate an NSURLBackgroundSession which pulls the desired data from your server and process it.
Your need to activate this in the project settings
1.) Add this in AppDelgate for receiving pushes
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
application.registerForRemoteNotifications()
}
When you get a push from a server or something else, following delegate method will be called: (so add this too in your appDelegate)
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void)
{
NSLog("Silent Push recevied")
let vc = ViewController()
vc.pullData() // call your pullData method...
}
The method which is called now needs to pull the data from your database. In this case you use a NSURLSession to download whatever you need.
2.) Go to the ViewController where the data get processed and add:
the NSURLSessionDownloadDelegate delegate with the required delegate methods
class vc : ViewController, NSURLSessionDownloadDelegate{
func viewDidLoad()
{
super.viewDidLoad()
}
func pullData()
{
let url = NSURL(string: "http://example.com")!
let urlRequest = NSURLRequest(URL: url, cachePolicy: .UseProtocolCachePolicy, timeoutInterval: 5)
let backgroundSessionConfiguration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("backgroundSessionIDX")
backgroundSession = NSURLSession(configuration: backgroundSessionConfiguration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
let task = backgroundSession.downloadTaskWithRequest(urlRequest)
task.resume()
}
}
When the downloadTask is successfully completed
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL)
{
}
will be called and you can process the data
let path = location.path
let data = NSFileManager.defaultManager().contentsAtPath(path!)
You can alternatively do this with a NSURLSessionDataTask
I would recommend that you take a look at Apple's documentation on Background Execution. It states all the possible ways to run code in the background. Each method comes with it's limitations, advantages and disadvantages and also Apple mentions:
In iOS, only specific app types are allowed to run in the background
If your iOS application does not communicate over Bluetooth (BTLE or with MFi certified devices) with some device (you could configure that Bluetooth device to send some event to the iOS device every 30 minutes and execute your code when that happens), then the most reliable way to run some code every 30 minutes is by using silent push notifications.
Silent push notifications can be configured so that they're not shown to the user, but allows your code to run. The limitations with push notifications are that the user needs an active internet connection to receive them and the user needs to give your app permission to use push notifications (you can request this at app's first run).
I noticed that your questions mentions I don't want to use push notifications, but unfortunately you don't really have a choice here.
For background
In your viewDidLoad() method
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
Add this method in your ViewController
#objc func appMovedToBackground() {
print("App moved to background!")
}
For Forground
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToForground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appMovedToForground() {
print("App moved to forground!")
}
Related
Like WhatsApp, I am going to implement a web interface for an iPhone application. If a user logged in on both environments(iPhone & web) and receives a message from another user, a notification is sent to the iPhone and web. When the user reads that message from the web interface, notification sent to iPhone should be removed as WhatsApp does even when the iPhone app is killed.
How can we remove that iPhone sent notification from backend? As I know we don't have control on iPhone app when app is killed by User.
Thanks in advance.
I don't know how whatsapp manages this but you can use silent notification to achieve the above feature.You need to turn on the background mode for silent notification.
When Silent notification comes it doesn't show up on screen like regular notification it will open your application even if your app in terminated mode. The system will provide some time to your app to perform certain task and you can do in that time frame only.
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print(" Entire message \(userInfo)")
print("Article avaialble for download: \(userInfo["articleId"]!)")
//let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(scheduleLocalNotification), userInfo: nil, repeats: false)
let state: UIApplication.State = application.applicationState
switch state {
case UIApplicationState.active:
print("If needed notify user about the message")
default:
registerBackgroundTask()
removeBadges()
}
completionHandler(UIBackgroundFetchResult.newData)
}
func registerBackgroundTask() {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskIdentifier.invalid)
}
func endBackgroundTask() {
print("Background task ended.")
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskIdentifier.invalid
}
func removeBadge() {
UIApplication.shared.applicationIconBadgeNumber = 0
UIApplication.shared.cancelAllLocalNotifications()
}
What's app is a VoIP app and it's always (in a way) awake in the background. So one can perform api calls to check status of the application messages.
If your app has a VoIP feature, you can benefit from it.
Try using VoIP Pushes, silent push notification wont work if app was intentionally terminated by user.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I want to make a request when the application enters the onBackground or onAppWillTerminate mode. I added an observer to AppDelegate method but did not work. Simply I want to send a request and push notification as a response. How can I do this? My request is not news or music, simple JSON.
`func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Fabric.with([Crashlytics.self])
GADMobileAds.configure(withApplicationID: "ca-app-pub-8446699920682817~7829108")
let onesignalInitSettings = [kOSSettingsKeyAutoPrompt: false]
//ONE SIGNAL
OneSignal.initWithLaunchOptions(launchOptions,
appId: "f5854e88-0b58-495f-8d3f-e7899202d",
handleNotificationAction: nil,
settings: onesignalInitSettings)
OneSignal.inFocusDisplayType = OSNotificationDisplayType.notification;
OneSignal.promptForPushNotifications(userResponse: { accepted in
print("User accepted notifications: \(accepted)")
})
UIApplication.shared.statusBarStyle = .lightContent
UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
mS.StartTimer()
//NotificationCenter.default.addObserver(self, selector:#selector(AppDelegate.applicationWillTerminate(_:)), name:NSNotification.Name.UIApplicationWillTerminate, object:nil)
NotificationCenter.default.addObserver(self, selector:#selector(AppDelegate.applicationWillEnterForeground(_:)), name:NSNotification.Name.UIApplicationWillEnterForeground, object:nil)
return true
}
func applicationWillTerminate(_ application: UIApplication) {
//mS.StartTimer()
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
self.mS.StartTimer()
if let vc = window?.rootViewController as? LiveScores{
vc.getLiveMatches(url : URL(string: "http://test.com/test/index.php/Service/lastLive"))
}
completionHandler(.newData)
}
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void){
completionHandler(UIBackgroundFetchResult.newData)
}
func applicationDidEnterBackground(_ application: UIApplication) {
if let vc = window?.rootViewController as? LiveScores{
vc.getLiveMatches(url : URL(string: "http://opucukgonder.com/tipster/index.php/Service/lastLive"))
}
mS.StartTimer()
}
func applicationWillEnterForeground(_ application: UIApplication) {
if let vc = window?.rootViewController as? LiveScores{
vc.getLiveMatches(url : URL(string: "http://opucukgonder.com/tipster/index.php/Service/lastLive"))
}
mS.StartTimer()
}
}`
Actually application is forcefully killed after 3-4 seconds when applicationWillTerminate is called.
so thats why API is not called.
One solution you can try is to add sleep at the end of the applicationWillTerminate function like this :
func applicationWillTerminate(_ application: UIApplication) {
//call API Here
// 5 is the number of seconds in which you estimate your request
// will be finished before system terminate the app process
sleep(5)
print("applicationWillTerminate")
}
If you want to contact the server when the app is going into the background, that is certainly possible. Implement UIApplicationDelegate.applicationDidEnterBackground. See Strategies for Handling App State Transitions for full details. This is a very common and supported operation and should be what you need.
However, it is not possible to contact the server when the app is killed in the background. applicationWillTerminate is generally never called in any case since iOS 4. Prior to iOS 4, there was no "background" mode. When the user pressed the home button, the app was immediately terminated after calling this method, and that's why it still exists. But it's all-but-useless in modern apps. (You can still get this ancient behavior by setting UIApplicationExitsOnSuspend in your Info.plist, but this does nothing to help your problem.)
When your application is killed in the background it is just killed. It is not woken up, and no methods are called on it. It is unceremoniously terminated and removed from memory. You cannot respond to this event. The same happens when the user force-quits your app from the app-switcher.
I'm a little curious about why you would want a push notification when the app goes into the background, and particularly when the app terminates. When I've seen people try to do this in the past, they often were trying to use push notifications to keep the app launched at all times. This is not possible. Even if you found a work-around to make it technically possible, it is explicitly forbidden by Apple.
I wanted to download data in background even when app is not running. Is it possible?
I have tried using background fetch but it is not working.
Please refer to the code below:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
UIApplication.shared.applicationIconBadgeNumber = 9
}
It gets called when app is running but not when app is killed
Unfortunately background fetch works for max 3 min after the app is deactivated or in the background. Except for VOIP, Location, Audio..ect
What you can do is send a remote push notification "according to a certain event taking place in your backend server" to your App so the user interacts with it and gets your app to the foreground.
As soon as the app is loaded to the foreground you can add an observer with a selector function in viewWillAppear to start fetching the data you need.
NotificationCenter.default.addObserver(self, selector:#selector(applicationWillEnterForeground(_:)), name:NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
Selector function:
func applicationWillEnterForeground(_ notification: NSNotification) {
print("Fetch data")
}
Then in viewWillDisappear remove the observer:
NotificationCenter.default.removeObserver(self)
So our app makes use of UILocalNotifications which are scheduled for every morning at 9am. The content of this message is dependant on an API call which I want to make 5 minutes before the notification is scheduled for.
So for example, the notification might be set to remind the user to do task x, but there is the chance that the user has already task x (via the online portal and not app), in which case we want to tell them to do task Y instead. For various reasons we don't have a push notification server set up yet but will soon, so this is an interim solution fo rhte purpose of testing.
So my question is: How can I schedule an API call to be made which replaces the immiment notification message depending on the response, even if the app is in the background, or even closed?
Thanks!
You have to use silent push notification. in payload you can have get various information from server. then schedule UILocalNotification at 9:00 AM.
When you are using silent push notification your app will invoke in background / terminated state ( Note - would not come foreground ) upto your UILocalNotification sound file plays ( max 30 seconds ), in this 30 seconds you can do API related work.
When UILocalNotification display in notification center, For example there are 2 buttons with notification "Accepted task" and "Later". So when you tap on though buttons even your app will invoke in background / terminated state, even you can open app and redirect to specific view controller. with both case you can do API related work.
If you keep your UILocalNotification object in NSUserDefault then you can also retrieve it on didFinishLaunchingWithOption, in case your device is restarted and UILocalNotification and further API work is very crucial.
Let me know you need any help for silent push notification, UILocalNotification, didFinishLaunchingWithOption or anything.
Referral note
https://www.raywenderlich.com/123862/push-notifications-tutorial
https://www.sinch.com/tutorials/ios8-apps-and-pushkit/
https://developer.apple.com/reference/pushkit
Source https://github.com/hasyapanchasara/PushKit_SilentPushNotification
Push kit implementation code
import UIKit
import PushKit
class AppDelegate: UIResponder, UIApplicationDelegate,PKPushRegistryDelegate{
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let types: UIRemoteNotificationType = [.Alert, .Badge, .Sound]
application.registerForRemoteNotificationTypes(types)
self. PushKitRegistration()
return true
}
//MARK: - PushKitRegistration
func PushKitRegistration()
{
let mainQueue = dispatch_get_main_queue()
// Create a push registry object
if #available(iOS 8.0, *) {
let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
// Set the registry's delegate to self
voipRegistry.delegate = self
// Set the push type to VoIP
voipRegistry.desiredPushTypes = [PKPushTypeVoIP]
} else {
// Fallback on earlier versions
}
}
#available(iOS 8.0, *)
func pushRegistry(registry: PKPushRegistry!, didUpdatePushCredentials credentials: PKPushCredentials!, forType type: String!) {
// Register VoIP push token (a property of PKPushCredentials) with server
let hexString : String = UnsafeBufferPointer<UInt8>(start: UnsafePointer(credentials.token.bytes),
count: credentials.token.length).map { String(format: "%02x", $0) }.joinWithSeparator("")
print(hexString)
}
#available(iOS 8.0, *)
func pushRegistry(registry: PKPushRegistry!, didReceiveIncomingPushWithPayload payload: PKPushPayload!, forType type: String!) {
// Process the received push
// From here you can schedule UILocalNotification
}
}
I followed the tutorial by google on https://firebase.google.com/docs/notifications/ios/console-topics#receive_and_handle_topic_messages to subscribe to a Firebase topic on my iOS app.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
FIRMessaging.messaging().subscribeToTopic("/topics/Notifications")
let homeViewController = UINavigationController(rootViewController: HomeViewController())
UINavigationBar.appearance().translucent = false
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.rootViewController = homeViewController
window?.makeKeyAndVisible()
return true
}
However, when I send a topic push notification out from the Firebase console. I could not receive any push notifications. But when I send out push notification to user segment from the console, the push is working perfectly. When I check the Xcode console, I am seeing this FIRMessaging error.
2016-05-31 11:11:47.893: <FIRMessaging/WARNING> Cannot subscribe to topic: /topics/Notifications with token: (null)
I've tried to search for this error but have no luck finding anything. I am not sure if this is the problem that is causing my app to not receive any push from topics.
Does anyone have this issue and know how to solve it?
Looks like maybe you're calling subscribeToTopic too early.
First, before you set up any Firebase call, make sure you call
FIRApp.configure()
That will ensure that all Firebase services are properly set up and initialized.
Next, you're going to need to wait just a bit to subscribe to topics. Your client needs to first register your app with both APNs and FCM to ensure that it can receive notifications. That involves a network call, which means you can't subscribe to topics when your app first launches.
Instead, I'd recommend putting that code into your application:didRegisterUserNotificationSettings handler instead. Something like this:
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
NSLog(#"Hooray! I'm registered!");
[[FIRMessaging messaging] subscribeToTopic:#"/topics/cool_users"];
}
Edit: And the Swift version...
func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {
print("Hooray! I'm registered!")
FIRMessaging.messaging().subscribeToTopic("/topics/swift_fans")
}
The accepted solution did not work for me. The token is not always available when application:didRegisterUserNotificationSettings: is called.
For example if application is freshly installed and starts for the first time FIRInstanceID.instanceID().token() returns nil.
You need to make sure application calls subscribeToTopic: after the token is available.
I ended up with creating a helper object that enqueues subscribeToTopic:, unsubscribeFrom: calls and executes them in FIFO order after the token arrives.
class FIRMessagingHelper {
private let queue: OperationQueue
init() {
queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.addOperation(TokenReadyOperation())
}
func subscribeTo(topic: String) {
queue.addOperation {
OperationQueue.main.addOperation({
FIRMessaging.messaging().subscribeToTopic(topic)
})
}
}
func unsubscribeFrom(topic: String) {
queue.addOperation {
OperationQueue.main.addOperation({
FIRMessaging.messaging().unsubscribeFromTopic(topic)
})
}
}
}
TokenReadyOperation waits until the token appears. AsynchronousOperation is used as the base class to minimize boilerplate.
class TokenReadyOperation : AsynchronousOperation {
override init() {
super.init()
NotificationCenter.default.addObserver(self,
selector: #selector(TokenReadyOperation.tokenRefreshed(notification:)),
name: .firInstanceIDTokenRefresh,
object: nil)
}
override func didStart() {
finishIfTokenAvailable()
}
private func finishIfTokenAvailable() {
guard FIRInstanceID.instanceID().token() != nil else { return }
markFinished()
}
/// Posted every time token changes
#objc private func tokenRefreshed(notification: Notification) {
finishIfTokenAvailable()
}
}
Few things to keep in mind:
App must call FIRApp.configure() or FIRApp.configureWithOptions(_:) prior making any Firebase calls (as Todd Kerpelman mentioned)
subscribeToTopic:, unsubscribeFrom: are not thread safe and must be executed on main thread
Topic names has to be in "/topics/*" format (as henmer mentioned)
Make sure to use different configuration plist for debug and App Store release of your app. See FIRApp.configureWithOptions(_:) documentation.
Date & Time should be current, otherwise the token may not be delivered.
Make sure to use the newest framework version. I had issues with notification delivery with the SDK released around January 2017.
My problem was not solved by calling subscribeToTopic after
func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {
instead it worked by calling subscribeToTopic after
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
this function get called when you get your push token from APNS not firebase.
Xcode 8.3.2
Swift 3.0