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.
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?
What is the best way to subscribe to a public database in CloudKit?
I have a table with persons. Every person contains a name and a location.
Once the location changes, the location is updated in CloudKit.
That part is working fine.
But I am not able to make it work to get a notification when there is a record update.
Some example would be really helpful, as I have looked into the possible option already.
I have looked into the options where I save the subscription in the database and also the CKModifySubscriptionsOperation option.
Currently, my code to subscribe looks like this:
let predicate = NSPredicate(format: "TRUEPREDICATE")
let newSubscription = CKQuerySubscription(recordType: "Person", predicate: predicate, options: [.firesOnRecordCreation, .firesOnRecordDeletion, .firesOnRecordUpdate])
let info = CKSubscription.NotificationInfo()
info.shouldSendContentAvailable = true
newSubscription.notificationInfo = info
database.save(newSubscription, completionHandler: {
(subscription, error) in
if error != nil {
print("Error Creating Subscription")
print(error)
} else {
userSettings.set(true, forKey: "subscriptionSaved")
}
})
Can someone also show me how my AppDelegate should look like?
I have added the didReceiveRemoteNotification function to my AppDelegate. I also called application.registerForRemoteNotifications(). This is how my didReceiveRemoteNotification function looks like:
The print is not even coming for me.
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("Notification!!")
let notification = CKNotification(fromRemoteNotificationDictionary: userInfo) as? CKDatabaseNotification
if notification != nil {
AppData.checkUpdates(finishClosure: {(result) in
OperationQueue.main.addOperation {
completionHandler(result)
}
})
}
}
Here are a few other things you can check:
= 1 =
Make sure the CloudKit container defined in your code is the same one you are accessing in the CloudKit dashboard. Sometimes we overlook what we selected in Xcode as the CloudKit container when we create and test multiple containers.
= 2 =
Check the Subscriptions tab in the CloudKit dashboard and make sure your Person subscription is being created when you launch your app. If you see it, try deleting it in the CK Dashboard and then run your app again and make sure it shows up again.
= 3 =
Check the logs in the CK Dashboard. They will show a log entry of type push whenever a push notification is sent. If it's logging it when you update/add a record in the CK Dashboard, then you know the issue lies with your device.
= 4 =
Remember that push notifications don't work in the iOS simulator. You need an actual device (or a Mac if you are making a macOS app).
= 5 =
Through extensive testing, I've found notifications are more reliable if you always set the alertBody even if it's blank. Like this:
let info = CKSubscription.NotificationInfo()
info.shouldSendContentAvailable = true
info.alertBody = "" //This needs to be set or pushes don't always get sent
subscription.notificationInfo = info
= 6 =
For an iOS app, my app delegate handles notifications like this:
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//Ask Permission for Notifications
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound], completionHandler: { authorized, error in
DispatchQueue.main.async {
if authorized {
UIApplication.shared.registerForRemoteNotifications()
}
}
})
return true
}
//MARK: Background & Push Notifications
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]{
let dict = userInfo as! [String: NSObject]
let notification = CKNotification(fromRemoteNotificationDictionary: dict)
if let sub = notification.subscriptionID{
print("iOS Notification: \(sub)")
}
}
//After we get permission, register the user push notifications
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
//Add your CloudKit subscriptions here...
}
}
Getting permission for notifications isn't required if you are only doing background pushes, but for anything the user sees in the form of a popup notification, you must get permission. If your app isn't asking for that permission, try deleting it off your device and building again in Xcode.
Good luck! : )
I am using RxCloudKit library, here's an a code snippet of how it handles query notifications -
public func applicationDidReceiveRemoteNotification(userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let dict = userInfo as! [String: NSObject]
let notification = CKNotification(fromRemoteNotificationDictionary: dict)
switch notification.notificationType {
case CKNotificationType.query:
let queryNotification = notification as! CKQueryNotification
self.delegate.query(notification: queryNotification, fetchCompletionHandler: completionHandler)
...
This method is called from func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void)
Before you can receive notifications, you will need to do the following -
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
application.registerForRemoteNotifications()
...
UPDATE:
Info.plist should contain the following -
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
Update: As Reinhard mentioned in his comment: you can in fact still subscribe to changes from the public database and manually import the changes into Core Data. Still I am unsure whether it is a good idea to rely on subscriptions in the public database if Apple specifically mentioned these differences
Original answer:
I don't think the accepted answer fully answers the question here.
Short answer would be that the CloudKit public database does not support subscriptions like the private database. Instead only a polling mechanism can be used. NSPersistentCloudKitContainer handles this automatically, but only updates very rarely.
This talk from WWDC2020 explains this in detail and I recommend watching it because there are other important details mentioned where public database differs from private database: https://developer.apple.com/wwdc20/10650
In the talk they mentioned thet a pull is initiated on each app start and after about 30 mins of application usage.
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)
I have been battling with this for a while now but I've finally succumbed to submitting a question to see if anyone else has had an issue.
Essentially I'm trying to use the Facebook Analytics push service and only seem to be receiving push notifications some of the time. I've been intercepting the API calls using charles proxy to ensure that push tokens are being sent correctly by the Facebook SDK and events are being logged as expected. From this perspective everything is fine.
Next I use the 'Push campaign setup verification' on the settings page to try and send push and in-app push tests, and these also work fine (cards are displayed correctly on the devices etc.) on the 3 test devices I'm using.
The problem arrises when trying to create a campaign to send pushes to a certain segment of the devices. The first device I tried with worked with no issues at all, but then subsequent devices (with no changes to the codebase at all) wouldn't receive any pushes from any campaigns that I set up. My target audience is 'Device OS is iOS' so I would expect that all iOS devices would receive the push from the campaign. This seemed a bit odd, so I deleted the app off all the devices and rebuilt them again (ensuring that the test push worked on all devices) and setup the campaign again, but this time even the first device no longer worked. In the events debugger I get a 'Push Notification Error' and an error_message of 'InvalidDeviceOS' which makes no sense at all.
I've been going back and fourth with this for a few hours now with no success. I can see the tokens being sent to Facebook, and I can see the events being logged in the event debugger, I can use the push test service in settings and all devices receive pushes without an issue, but as soon as I try to use a campaign I get nothing.
For completion purposes here are the snippets of code I'm using:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// ...
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil))
// ...
}
func application(application: UIApplication, didRegisterUserNotificationSettings settings: UIUserNotificationSettings) {
UIApplication.sharedApplication().registerForRemoteNotifications()
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
FBSDKAppEvents.setPushNotificationsDeviceToken(deviceToken)
}
Finally here is the events debugger after opening a fresh install on 3 different devices (see the first launch events, 2 app launches have ID's and 1 doesn't as it's not logged in yet).
Any help with this would be great, thanks.
UPDATE: I created a brand new app and a brand new Facebook analytics instance, new push certificates etc. Initially I started receiving pushes from campaigns, but once I deleted and reinstalled the app the campaign messaging started to fail again. I then installed it onto a different device and cloned the existing campaign and tried again. This then resulted in the original device getting the push but the new device didn't... the mind boggles!
Now I don't know whats going on. On the face of it the Facebook service seems to get flaky once a push error comes back from APNS, but it's really difficult to prove that assumption since the error reporting in the Event Debugger is next to useless.
Again for completeness here is the code from the new app:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
application.registerUserNotificationSettings(UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil))
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
FBSDKAppEvents.activateApp()
}
// MARK: Notification Methods
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
application.registerForRemoteNotifications()
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
FBSDKAppEvents.setPushNotificationsDeviceToken(deviceToken)
FBSDKAppEvents.setUserID(NSUUID().uuidString)
var token: String = ""
for i in 0 ..< deviceToken.count {
token += String(format: "%02.2hhx", deviceToken[i] as CVarArg)
}
print("Token: \(token)")
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if FBNotificationsManager.shared().canPresentPushCard(fromRemoteNotificationPayload: userInfo) {
FBNotificationsManager.shared().presentPushCard(forRemoteNotificationPayload: userInfo, from: nil, completion: nil)
} else {
print("Unknown Payload")
}
completionHandler(.newData)
}
UPDATE 2: After hours of experimenting and digging we came across this bug report on Facebook's support pages which seems to confirm this is not an isolated issue: https://developers.facebook.com/bugs/118059088679219/
for testing I use OneSignal service for send push notification on my device and I handle it in AppDelegate in this way:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
OneSignal.initWithLaunchOptions(launchOptions, appId: “[app ID]”)//this method I register device on apple server
return true
}
func application(application: UIApplication,
didReceiveRemoteNotification userInfo: [NSObject : AnyObject],
fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void){
print(“ARRIVED")
handleNotificationContent()// it’s not important for my question
}
My problem is that when I receive a notification and the app is in foreground , alert shows automatically and I don’t want to show it.
How do I solve this problem?
This code worked for me.
This is applicable for Swift 2.2 and Xcode 7.3.1
//Initialize One Signal using this code
OneSignal.initWithLaunchOptions(launchOptions, appId: oneSignalId, handleNotificationReceived: { (notification) in
//Put your business logic here like adding an alert controller or posting an NSNotification.
}, handleNotificationAction: { (nil) in
// This block gets called when the user reacts to a notification received
}, settings: [kOSSettingsKeyAutoPrompt : false, kOSSettingsKeyInAppAlerts: false])
//set kOSSettingsKeyAutoPrompt to false
You need to set the kOSSettingsKeyInFocusDisplayOption to None in initWithLaunchOptions, to disable the automatic display of inapp alerts.
OneSignal Api Reference