I'm writing an App in Swift where I have implemented a singleton class handling the data retrieval via Moscapsule.
When I inform my app that there is new data with content-available: 1 the right function gets called where I'm currently only doing
let mqtt = MQTTManager.Instance
completionHandler(UIBackgroundFetchResult.NewData)
The manager creates a connection on init and the onMessageCallback then works with the new data.
If the app has run recently, is in background and the singleton class keeps up the network connection, this works. But this is not the right way.
I'm very new to iOS and Swift development, how can I wait the maximum amount of time? When the data comes in I could then send UIBackgroundFetchResult.NewData, if nothing comes, I could then send UIBackgroundFetchResult.Failed.
My first idea was to write a loop checking a variable from that class if a new message has come in every 500ms, and if so, call the completionHandler. But this doesn't feel right to me, so, what would you do?
EDIT 2015/02/18:
Heres the code from my AppDelegate.swift that gets executed once the app receives the silent push notification:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
log.debug("Got remote notification: \(userInfo)")
log.debug("Setting up MQTT client...")
let mqtt = MQTTManager.sharedInstance
completionHandler(UIBackgroundFetchResult.NewData)
}
You could define a property within that class to hold the completion handler (making it an optional):
var completionHandler: ((UIBackgroundFetchResult) -> Void)?
Then your didReceiveRemoteNotification would do something like:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
log.debug("Got remote notification: \(userInfo)")
log.debug("Setting up MQTT client...")
let mqtt = MQTTManager.sharedInstance
mqtt.completionHandler = completionHandler
}
Then, the onMessageCallback could then do something like:
self.completionHandler?(.NewData) // or .NoData or whatever
self.completionHandler = nil
I must confess that this doesn't feel right. It feels like the init function is not only instantiating the singleton, but also starting a connection, making a request, what have you. You haven't shared it with us, so it's hard to say, but it feels incorrect. The init should really only be creating the singleton. Then, let us configure it (e.g. specify the completionHandler) and only then would it call the separate connect function.
Related
I need to call a notification so that the phone vibrates and rings for several minutes at a certain point in time, planned for earlier.
To send a notification, I use UNNotificationRequest. after searching, I realized that you can only change .sound, having muted the sound for 30 seconds. Which does not fit.
I thought to make several notifications at intervals of a second, but there is a limit on the number of notifications (64 pieces), and this is not enough. Then I tried to cause vibration with
func application (_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if application.applicationState == .background {
for _ in 1 ... 30 {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
sleep(1)
}
completionHandler(.newData)
}
}
But the problem is that I do not know, how to call this function at a certain point in time, and even if the application is killed.
+ There is a problem with the fact that when you open the application, the vibration does not pass.
+ performFetch more than anything else for network requests.
Question: I need to attract the user's attention with vibration + melody (if the mode allows it) + notification. How can this be done, and in which direction should I continue to dig?
Maybe all the same I somehow can wake the application up in time and start the vibration + melody. A notification will arrive from the system itself.
UPD 1: from the answer https://stackoverflow.com/a/43232737/13642906
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) { }
don`t work on ios10 and above.
Now (ios 10 and above) use
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) { }
but its method dont call when not called when local notification arrives. Maybe, am I doing something wrong?
Is it possible to do what I want?
Is that possible to call API to send data to the server while getting silent push-notification in iOS? Any help will be appreciated.
Thanks in advance
It is possible to make an API call after receiving a silent push.However, it does not work if the app is killed by the user. If that is a problem, please see this answer
If that is not the case, here is how to do it. You need to enable 'Background Fetch' and 'Remote Notifications' from Background Modes on Application Capabilities screen on XCode.
Then, add this application(_:didReceiveRemoteNotification:fetchCompletionHandler:) method into your AppDelegate.You can make your API call inside this method.
Ex:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
apiCall(fetchCompletionHandler: completionHandler)
}
func apiCall(fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void){
//Make call here
completionHandler(UIBackgroundFetchResult.noData)
}
I know there has been a lot written about this topic but I just can't find the right answer.
Is there a way how to know when the user received remote notification and when the user clicked on one on iOS 8.
I would like to know this because when I receive it I want to save it and when user clicks on it I want to open some view.
I have found this answer https://stackoverflow.com/a/16393957/1241217 but the problem is when user is in the app and opens notification center and clicks on one, the app is not inactive and not in the background.
I also found this answer https://stackoverflow.com/a/12937568/1241217 but I know that this is ran only when the app is killed and started from new.
I also don't want to do this https://stackoverflow.com/a/32079458/1241217 since I need to detect when I received notification.
So is there a way how to know if the user only clicked on notification. As far as I understood it has to be done in didReceiveRemoteNotification but I don't know how to separate between them. And I need an answer for before iOS 10 because the app target is iOS 8.
MY SOLUTION:
So as I wrote in the comment of Shabbir Ahmad answer my solution was to remember date when the application did become active and the date when the notification was received. If the difference between this dates was a second or less I accepted that as the user clicked on the notification.
You have to implement UNUserNotificationCenterDelegate and its method
userNotificationCenter(_:willPresent:withCompletionHandler:) and userNotificationCenter(_:didReceive:withCompletionHandler:) which gets called when a user taps a notification. In willPresent: you have to call the completionHandler with an option that would indicate what should happen when a notification arrives while the app is in foreground.
Registering such a delegate is easy:
UNUserNotificationCenter.current().delegate = self
So e.g.:
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(UNNotificationPresentationOptions.alert)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let userInfo = userInfo as? [String: Any] {
// TODO: implement your logic
// just don't forget to dispatch UI stuff on main thread
}
}
You can implement that delegate by AppDelegate, but also by any NSObject, I would go with the latter to keep AppDelegate as clean as possible.
P.S.: Of course, this assumes that you have been granted permissions by the user (UNUserNotificationCenter.current().requestAuthorization(options:completionHandler:)) and you are registered to accept notifications (UIApplication.shared.registerForRemoteNotifications()).
Read more in Scheduling and Handling Local Notifications, section Responding to the Delivery of Notifications - while the section is about local notifications, it is exactly the same for the remote ones (they are handled both by the same delegate).
when you click on notification in background mode before ios 10 and when you are in foreground,in both cases your below method will call,
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void)
So you can differentiate the behaviour,
First of all you assign a boolean variable in AppDelegate class like this:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var isUserTapOnNotification = false
after that make true isUserTapOnNotification in
func applicationWillEnterForeground(_ application: UIApplication) {
isUserTapOnNotification = tue
}
because when you tap on notification bar, your app will came in foreground and applicationWillEnterForeground will call first,
after that your didReceiveRemoteNotification will call:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if #available(iOS 10.0, *) {
//do nothing
}else { //<ios 10
if isUserTapOnNotification == true {//when app is in background and user tap on notification bar
//do action whatever you want
} else { //when user is in foreground and notification came,
//before ios10,notification bar not display in foreground mode,So you can show popup by using userInfo
}
}
after that applicationDidBecomeActive will call and you reset isUserTapOnNotification to false like this:
func applicationDidBecomeActive(_ application: UIApplication) {
isUserTapOnNotification = false
}
I hope this answer will help you.
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)
App remote notification work well when app is foreground or background state but not works when App is killed manually,
Added background fetch in plist.
Tried with:
NSLog("Do something")
under this method still not able to receive in swift :
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void)
and
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void)
When you kill the app manually, the remoteNotifications cannot be handled before the app is launched again.
When a remote notification is present, and you open the app by tapping on that notification, the app launches in the usual manner with the
didFinishLaunchingWithOptions
method being called in the app delegate. But in this case, your notification data would be passed in the 'Options' parameter. You can then check for this parameter and perform the required tasks
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
if (launchOptions != nil)
{
let dictionary:NSDictionary = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as! NSDictionary
self.setNotification(dictionary)
}
return true
}
You cannot handle a push notifications while app is not running. You only can handle when a user open the app form notification.
There is good article about that: How to handle remote notification with background mode enabled