iOS 10 introduced messages extensions, which are the first (to my knowledge) extension which does not require a host application. I am trying to use CloudKit in a messages extension which does not have a host app.
From what I can tell, CKSubscription relies on push notifications. However, I cannot register for push notifications in the usual way (via UIApplication) in app extensions:
let app = UIApplication.shared // Error: not available here blah blah blah
This means it is seemingly impossible to receive CKSubscription notifications in a messages app. I did find hope in the new UserNotifications.framework, but it does not provide any mechanisms for registering for remote notifications. I tried:
override func willBecomeActive(with conversation: MSConversation) {
// ...
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.badge, .alert]) { success, error in
if error != nil { fatalError() }
}
center.delegate = self
// ...
}
// MARK: UNUserNotificationCenterDelegate
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void) {
fatalError() // This never gets called :(
}
But when I update records that are the subject of my CKSubscription, no notification is presented to the user and the delegate is not notified.
Here's my CKSubscription code:
let sub = CKRecordZoneSubscription(zoneID: legitimateZone)
let notifInfo = CKNotificationInfo()
notifInfo.alertBody = "Wow it works! Amazing!"
sub.notificationInfo = notifInfo
privateDB.save(sub) { sub, error in
if let e = error {
fatalError("Error saving zone sub: \(e)")
}
print("Saved subscription")
}
How do I get CKSubscription notifications in a messages extension?
I don't even need the notification presented to the user, nor do I need to receive them in the background. All I want is to know when records are updated while my extension is running.
If there is another way to do this other than CKSubscription I'd love to hear as well (as long as it's not going to poll CloudKit constantly, wasting my precious 40 requests/sec).
I have tried on both a physical device and in the simulator.
Related
I am redeveloping an android app for iOS with SwiftUI that contains a countdown feature. When the countdown finishes the user should be noticed about the end of the countdown. The Notification should be somewhat intrusive and work in different scenarios e.g. when the user is not actively using the phone, when the user is using my app and when the user is using another app. I decided to realize this using Local Notifications, which is the working approach for android. (If this approach is totally wrong, please tell me and what would be best practice)
However I am stuck receiving the notification when the user IS CURRENTLY using my app. The Notification is only being shown in message center (where all notifications queue) , but not actively popping up.
Heres my code so far:
The User is being asked for permission to use notifications in my CountdownOrTimerSheet struct (that is being called from a different View as actionSheet):
/**
asks for permission to show notifications, (only once) if user denied there is no information about this , it is just not grantedand the user then has to go to settings to allow notifications
if permission is granted it returns true
*/
func askForNotificationPermission(userGrantedPremission: #escaping (Bool)->())
{
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
userGrantedPremission(true)
} else if let error = error {
userGrantedPremission(false)
}
}
}
Only if the user allows permission for notification my TimerView struct is being called
askForNotificationPermission() { (success) -> () in
if success
{
// permission granted
...
// passing information about the countdown duration and others..
...
userConfirmedSelection = true // indicates to calling view onDismiss that user wishes to start a countdown
showSheetView = false // closes this actionSheet
}
else
{
// permission denied
showNotificationPermissionIsNeededButton = true
}
}
from the previous View
.sheet(isPresented: $showCountDownOrTimerSheet, onDismiss: {
// what to do when sheet was dismissed
if userConfirmedChange
{
// go to timer activity and pass startTimerInformation to activity
programmaticNavigationDestination = .timer
}
}) {
CountdownOrTimerSheet(startTimerInformation: Binding($startTimerInformation)!, showSheetView: $showCountDownOrTimerSheet, userConfirmedSelection: $userConfirmedChange)
}
...
NavigationLink("timer", destination:
TimerView(...),
tag: .timer, selection: $programmaticNavigationDestination)
.frame(width: 0, height: 0)
In my TimerView's init the notification is finally registered
self.endDate = Date().fromTimeMillis(timeMillis: timerServiceRelevantVars.endOfCountDownInMilliseconds_date)
// set a countdown Finished notification to the end of countdown
let calendar = Calendar.current
let notificationComponents = calendar.dateComponents([.hour, .minute, .second], from: endDate)
let trigger = UNCalendarNotificationTrigger(dateMatching: notificationComponents, repeats: false)
let content = UNMutableNotificationContent()
content.title = "Countdown Finished"
content.subtitle = "the countdown finished"
content.sound = UNNotificationSound.defaultCritical
// choose a random identifier
let request2 = UNNotificationRequest(identifier: "endCountdown", content: content, trigger: trigger)
// add the notification request
UNUserNotificationCenter.current().add(request2)
{
(error) in
if let error = error
{
print("Uh oh! We had an error: \(error)")
}
}
As mentioned above the notification gets shown as expected when the user is everyWhere but my own app. TimerView however displays information about the countdown and is preferably the active view on the users device. Therefore I need to be able to receive the notification here, but also everywhere else in my app, because the user could also navigate somewhere else within my app. How can this be accomplished?
In this example a similar thing has been accomplished, unfortunately not written in swiftUI but in the previous common language. I do not understand how this was accomplished, or how to accomplish this.. I did not find anything on this on the internet.. I hope you can help me out.
With reference to the documentation:
Scheduling and Handling Local Notifications
On the section about Handling Notifications When Your App Is in the Foreground:
If a notification arrives while your app is in the foreground, you can
silence that notification or tell the system to continue to display
the notification interface. The system silences notifications for
foreground apps by default, delivering the notification’s data
directly to your app...
Acording to that, you must implement a delegate for UNUserNotificationCenter and call the completionHandler telling how you want the notification to be handled.
I suggest you something like this, where on AppDelegate you assign the delegate for UNUserNotificationCenter since documentation says it must be done before application finishes launching (please note documentation says the delegate should be set before the app finishes launching):
// AppDelegate.swift
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
UNUserNotificationCenter.current().delegate = self
return true
}
}
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
// Here we actually handle the notification
print("Notification received with identifier \(notification.request.identifier)")
// So we call the completionHandler telling that the notification should display a banner and play the notification sound - this will happen while the app is in foreground
completionHandler([.banner, .sound])
}
}
And you can tell SwiftUI to use this AppDelegate by using the UIApplicationDelegateAdaptor on your App scene:
#main
struct YourApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
This approach is similar to Apple's Fruta: Building a Feature-Rich App with SwiftUI
https://developer.apple.com/documentation/swiftui/fruta_building_a_feature-rich_app_with_swiftui
Apple have used In-app purchases this way
This class holds all your code related to Notification.
class LocalNotificaitonCenter: NSObject, ObservableObject {
// .....
}
In your #main App struct, define LocalNotificaitonCenter as a #StateObject and pass it as an environmentObject to sub-views
#main
struct YourApp: App {
#Environment(\.scenePhase) private var scenePhase
#StateObject var localNotificaitonCenter = LocalNotificaitonCenter()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(localNotificaitonCenter)
}
}
}
It is just that!
I'm using Firebase Messaging (Notifications) to send push reminders to users on iOS. For my app, that is a todo app, I'm using Swift 3. When the user gets the push notification I want them to be able to complete the task right from the push notification.
Everything works almost great. The user gets the push. When they 3d-touch they see the "complete button". When the "complete button" is tapped the didReceive response method in the app is triggered in the background.
Now to the problem, in that method I'm using a closure and then a closure in that closure. For some reason the first part of the code runs in the background without the user opening the app but the last part is only running when the user opens the app again (see below). Why is that?
This is my code:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if response.actionIdentifier == notificationActionComplete, let actionKey = userInfo["actionKey"] as? String {
getAction(actionKey: actionKey, completion: { (action) in
action.complete {
}
})
}
completionHandler()
}
func getAction(actionKey: String, completion:#escaping (Action)->Void) {
Database.database().reference(withPath: "actions/\(actionKey)").observeSingleEvent(of: .value, with: { snapshot in
let action = Action(snapshot: snapshot)
completion(action)
})
}
In action class:
var ref: DatabaseReference?
init(snapshot: DataSnapshot) {
key = snapshot.key
ref = snapshot.ref
//Other inits here
}
func complete(completion:#escaping (Void) -> Void) {
//This code to remove the node is running fine in background
ref.removeValue { (error, ref) in
//The code in here is not running until the user opens the app next time
otherRef.updateChildValues(self.toAnyObject(), withCompletionBlock: { (error, ref) in
completion()
})
}
Your app is basically suspended after the runloop cycle where userNotificationCenter() is called, so if your completion handler is in response to asynchronous work, that work will never happen until your app resumes again. To get around this you will probably need to begin a background task inside that function, and then have your completion handler end the background task when it is finished. This tells the system you need to stay alive for a while in the background (although it is not guaranteed, if you take too long)
See "Executing Finite Limit Tasks" at this URL (sorry, it's Obj-C, but there should be a Swift way to do it too):
https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
I have tried to implement background fetch, to hopefully can wake the app from time to time.
I have done these:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
return true
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
debugPrint("performFetchWithCompletionHandler")
getData()
completionHandler(UIBackgroundFetchResult.newData)
}
func getData(){
debugPrint("getData")
}
I have also enable background fetch capabilities already. That's all i have done. And then i run the app. the function never called even after an hour (the phone slept).
What other things i have to do to make the function get called?
You have done many of the necessary steps:
Turned on "background fetch" the the "Capabilities" tab of your project;
Implemented application(_:performFetchWithCompletionHandler:);
Called setMinimumBackgroundFetchInterval(_:) in application(_:didFinishLaunchingWithOptions:).
That having been said, a couple of observations:
I'd check the permissions for the app in "Settings" » "General" » "Background App Refresh". This ensures that not only did you successfully request background fetch in your plist, but that it's enabled in general, as well as for your app in particular.
Make sure you're not killing the app (i.e. by double tapping on the home button and swiping up on your app for force the app to terminate). If the app is killed, it will prevent background fetch from working correctly.
You're using debugPrint, but that only works when running it from Xcode. But you should be doing this on a physical device, not running it from Xcode. You need to employ a logging system that shows you activity even when not running the app through Xcode.
I use os_log and watch it from the Console (see WWDC 2016 Unified Logging and Activity Tracing) or use post a notification via the UserNotifications framework (see WWDC 2016 Introduction to Notifications) so I'm notified when app does something notable in the background. Or I've created my own external logging systems (e.g. writing to some text file or plist). But you need some way of observing the activity outside of print/debugPrint because you want to test this while not running it independently of Xcode. Any background-related behaviors change while running an app connected to the debugger.
As PGDev said, you don't have control over when the background fetch takes place. It considers many poorly documented factors (wifi connectivity, connected to power, user's app usage frequency, when other apps might be spinning up, etc.).
That having been said, when I enabled background fetch, ran the app from the device (not Xcode), and had it connected to wifi and power, the first background fetch called appeared on my iPhone 7+ within 10 minutes of suspending the app.
Your code isn't currently doing any fetch request. That raises two concerns:
Make sure that the test app actually issues URLSession request at some point its normal course of action when you run it (i.e. when you run the app normally, not via background fetch). If you have a test app that doesn't issue any requests, it doesn't appear to enable the background fetch feature. (Or at the very least, it severely affects the frequency of the background fetch requests.)
Reportedly, the OS will stop issuing subsequent background fetch calls to your app if prior background fetch calls didn't actually result in a network request being issued. (This may be a permutation of the prior point; it's not entirely clear.) I suspect Apple is trying to prevent developers using background fetch mechanism for tasks that aren't really fetching anything.
Note, your app doesn't have much time to perform the request, so if you are issuing a request, you might want to inquire solely whether there is data available, but not try to download all the data itself. You can then initiate a background session to start the time consuming downloads. Obviously, if the amount of data being retrieved is negligible, then this is unlikely to be a concern, but make sure you finish your request call the background completion reasonably quickly (30 seconds, IIRC). If you don't call it within that timeframe, it will affect if/when subsequent background fetch requests are attempted.
If the app is not processing background requests, I might suggest removing the app from the device and reinstalling. I've had situation where, when testing background fetch where the requests stopped working (possibly as a result of a failed background fetch request when testing a previous iteration of the app). I find that removing and re-installing it is a good way to reset the background fetch process.
For sake of illustration, here is an example that performs background fetches successfully. I've also added UserNotifications framework and os_log calls to provide a way of monitoring the progress when not connected to Xcode (i.e. where print and debugPrint no longer are useful):
// AppDelegate.swift
import UIKit
import UserNotifications
import os.log
#UIApplicationMain
class AppDelegate: UIResponder {
var window: UIWindow?
/// The URLRequest for seeing if there is data to fetch.
fileprivate var fetchRequest: URLRequest {
// create this however appropriate for your app
var request: URLRequest = ...
return request
}
/// A `OSLog` with my subsystem, so I can focus on my log statements and not those triggered
/// by iOS internal subsystems. This isn't necessary (you can omit the `log` parameter to `os_log`,
/// but it just becomes harder to filter Console for only those log statements this app issued).
fileprivate let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "log")
}
// MARK: - UIApplicationDelegate
extension AppDelegate: UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// turn on background fetch
application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
// issue log statement that app launched
os_log("didFinishLaunching", log: log)
// turn on user notifications if you want them
UNUserNotificationCenter.current().delegate = self
return true
}
func applicationWillEnterForeground(_ application: UIApplication) {
os_log("applicationWillEnterForeground", log: log)
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
os_log("performFetchWithCompletionHandler", log: log)
processRequest(completionHandler: completionHandler)
}
}
// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
os_log("willPresent %{public}#", log: log, notification)
completionHandler(.alert)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
os_log("didReceive %{public}#", log: log, response)
completionHandler()
}
}
// MARK: - Various utility methods
extension AppDelegate {
/// Issue and process request to see if data is available
///
/// - Parameters:
/// - prefix: Some string prefix so I know where request came from (i.e. from ViewController or from background fetch; we'll use this solely for logging purposes.
/// - completionHandler: If background fetch, this is the handler passed to us by`performFetchWithCompletionHandler`.
func processRequest(completionHandler: ((UIBackgroundFetchResult) -> Void)? = nil) {
let task = URLSession.shared.dataTask(with: fetchRequest) { data, response, error in
// since I have so many paths execution, I'll `defer` this so it captures all of them
var result = UIBackgroundFetchResult.failed
var message = "Unknown"
defer {
self.postNotification(message)
completionHandler?(result)
}
// handle network errors
guard let data = data, error == nil else {
message = "Network error: \(error?.localizedDescription ?? "Unknown error")"
return
}
// my web service returns JSON with key of `success` if there's data to fetch, so check for that
guard
let json = try? JSONSerialization.jsonObject(with: data),
let dictionary = json as? [String: Any],
let success = dictionary["success"] as? Bool else {
message = "JSON parsing failed"
return
}
// report back whether there is data to fetch or not
if success {
result = .newData
message = "New Data"
} else {
result = .noData
message = "No Data"
}
}
task.resume()
}
/// Post notification if app is running in the background.
///
/// - Parameters:
///
/// - message: `String` message to be posted.
func postNotification(_ message: String) {
// if background fetch, let the user know that there's data for them
let content = UNMutableNotificationContent()
content.title = "MyApp"
content.body = message
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let notification = UNNotificationRequest(identifier: "timer", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(notification)
// for debugging purposes, log message to console
os_log("%{public}#", log: self.log, message) // need `public` for strings in order to see them in console ... don't log anything private here like user authentication details or the like
}
}
And the view controller merely requests permission for user notifications and issues some random request:
import UIKit
import UserNotifications
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// request authorization to perform user notifications
UNUserNotificationCenter.current().requestAuthorization(options: [.sound, .alert]) { granted, error in
if !granted {
DispatchQueue.main.async {
let alert = UIAlertController(title: nil, message: "Need notification", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
// you actually have to do some request at some point for background fetch to be turned on;
// you'd do something meaningful here, but I'm just going to do some random request...
let url = URL(string: "http://example.com")!
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
let alert = UIAlertController(title: nil, message: error?.localizedDescription ?? "Sample request finished", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true)
}
}
task.resume()
}
}
Background Fetch is automatically initiated by the system at appropriate intervals.
A very important and cool feature of the Background Fetch is its
ability to learn the times that should allow an app to be launched to
the background and get updated. Let’s suppose for example that a user
uses a news app every morning about 8:30 am (read some news along with
some hot coffee). After a few times of usage, the system learns that
it’s quite possible that the next time the app will run will be around
the same time, so it takes care to let it go live and get updated
before the usual launch time (it could be around 8:00 am). That way,
when the user opens the app the new and refreshed content is there
awaiting for him, and not the opposite! This feature is called usage
prediction.
For testing whether the code you wrote works properly or not, you can refer to Raywenderlich's tutorial on Background Fetch.
Tutorial: https://www.raywenderlich.com/143128/background-modes-tutorial-getting-started
(Search for: Testing Background Fetch)
I have a weather app for iOS, and I'd like to allow the user to receive a notification each morning at a time of their choosing which would fetch the weather forecast for the day and display a notification.
I'd like to avoid using push notifications, and I thought I might be able to use local notifications, except I can't see a way to fetch the content to be shown from a server. It looks like the content has to be set at the time of scheduling. Is that right?
That makes me think I might be able to register my application to use background execution to periodically fetch the weather and schedule a notification with the latest content, but this seems wasteful.
In short, I'd like to tell iOS to run a specific function at a specific time. Is there a good option for this that I'm missing? Are push notifications the only/best way to accomplish this sort of thing?
Push notification is best option for your if you want to display weather forecast .
More about this : https://stackoverflow.com/a/41901767/3901620
You can schedule a local notification for a specific time and when a user sees it and if he wants he can open your app by tapping on that notification. At that time, you will able to know that, a user has tapped on a notification and thus the app is open, you can make a network call to fetch the data and show it inside the application. This will not require any background calls therefor and only make a network call to an action by a user.
Another option: You can create a widget of your app (like Weather Widget). Whenever a user goes into widget area you will get a delegate call and make a network call to get the latest weather data. If a user wants more information on it, he can simply tap on it and your app will open. Then, everything will be in your hands.
Your option: You can always get dynamic content whenever the user opens your app for a particular date and set a notification for it. But this is not suggestible as the user may not get updated data.
Push Notification: This may not be required with your case, however, if you want to send the dynamic data over your server to your app. This is always the best option.
i have created a function. In which this will call your function at a specific time, when you want. Am creating a clock app so i need to trigger a local notification when ever user created the alarm. And in the notification Center Delegate method, you can handle your response and call the whatever method you want.
class LocalNotificationMethod : NSObject {
static let notificationInstance = LocalNotificationMethod()
let requestIdentifier = "SampleRequest" //identifier is to cancel the notification request
internal func scheduleLocalNotification(titleOfNotification:String, subtitleOfNotification:String, messageOfNotification:String, soundOfNotification:String, dateOfNotification:String) {
if #available(iOS 10.0, *) {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd hh:mm a"
let date3 = formatter.date(from: dateOfNotification)
let content = UNMutableNotificationContent()
content.body = NSString.localizedUserNotificationString(forKey: titleOfNotification, arguments: nil)
content.sound = soundOfNotification.characters.count > 0 ? UNNotificationSound.init(named: soundOfNotification + ".mp3") : UNNotificationSound.default()
let trigger = UNCalendarNotificationTrigger.init(dateMatching: NSCalendar.current.dateComponents([.day, .month, .year, .hour, .minute], from: date3!), repeats: false)
let request = UNNotificationRequest(identifier:requestIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request){(error) in
if (error != nil){
print(error?.localizedDescription)
} else {
print("Successfully Done")
}
}
} else {
// Fallback on earlier versions
}
}
}
And in AppDelegate Methods : - You can handle whenever user click on your notification or whenever your notification will be present.Is up to you what you want to done.
//MARK:- Notification Delegates
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print("Tapped in notification")
}
//This is key callback to present notification while the app is in foreground
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("Notification being triggered")
//You can either present alert ,sound or increase badge while the app is in foreground too with ios 10
//to distinguish between notifications
if notification.request.identifier == "SampleRequest" {
completionHandler( [.alert,.sound,.badge])
}
}
I have been trying to use a Firebase listener to trigger local notifications. I have found a post that addresses exactly what I am trying to do with much of it explained, however I do not have the reputation to comment on the post and there seems to be no indication of how to accomplish what I want anywhere else.
The original poster says this.
I figured it out! I had to use a different approach but i was able to
get my Firebase Database observer to trigger notifications in the
background.
As long as the object containting the database observer is not
deallocated from memory it will continue to observe and trigger. So I
created a global class which contains a static database object
property like this:
class GlobalDatabaseDelegate {
static let dataBase = DataBase()
}
This is where I am confused as to what to do for my own project. It is my understanding that I have to create a class similar to DataBase() which contains my database reference. The problem is I do not understand how to create class object that will contain the database listener.
say for example my reference is :
let userRef = FIRDatabase.database.reference().child("users")
And I want to observe any users added to the database and then trigger a local notification. I am able to write the code to do so, just not sure how to contain it in an object class of its own and then make it static.
Forgive me for being a little slow. Any help would be very much appreciated.
The rest of the post follows :
I also extended the DataBase class to be the
UNUserNotificationCenterDelegate so it can send the push notitications
like this:
extension DataBase: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print("Tapped in notification")
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("Notification being triggered")
completionHandler( [.alert,.sound,.badge])
}
func observeNotificationsChildAddedBackground() {
self.notificationsBackgroundHandler = FIREBASE_REF!.child("notifications/\(Defaults.userUID!)")
self.notificationsBackgroundHandler!.queryOrdered(byChild: "date").queryLimited(toLast: 99).observe(.childAdded, with: { snapshot in
let newNotificationJSON = snapshot.value as? [String : Any]
if let newNotificationJSON = newNotificationJSON {
let status = newNotificationJSON["status"]
if let status = status as? Int {
if status == 1 {
self.sendPushNotification()
}
}
}
})
}
func sendPushNotification() {
let content = UNMutableNotificationContent()
content.title = "Here is a new notification"
content.subtitle = "new notification push"
content.body = "Someone did something which triggered a notification"
content.sound = UNNotificationSound.default()
let request = UNNotificationRequest(identifier: "\(self.notificationBackgroundProcessName)", content: content, trigger: nil)
NotificationCenter.default.post(name: notificationBackgroundProcessName, object: nil)
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().add(request){ error in
if error != nil {
print("error sending a push notification :\(error?.localizedDescription)")
}
}
}
}
In essence I am trying to keep a firebase listener in memory when the app is in background.
So the original post that I have linked in has the answer but it is a matter of understanding it. I have also implemented my code in a slightly different approach.
I found another post detailing the technique needed to run a custom data service class. Custom Firebase Data Service Class : Swift 3
To set keep the firebase listener in memory there are few steps.
1.Create a firebase data service class. In that class I have a static variable that is of the same class
class FirebaseAPI {
var isOpen = false
static let sharedInstance = FirebaseAPI()
// I added functions for firebase reference in this class
func observeNotifications(){
//firebase call here
}
}
2.Set up notification settings in app delegate. This is where my set up differs from the original post.
let notificationSettings = UIUserNotificationSettings(types: [.badge, .alert, .sound], categories: nil)
UIApplication.shared.registerUserNotificationSettings(notificationSettings)
3.Create a reference to the firebase class in a viewcontroller of your choice, it works in app delegate but not advisable.
let sharedInstance = FirebaseAPI.sharedInstance
4.Call functions to setup observer
self.sharedInstance.observeNotifications()
You can then trigger fire a local notification using a completion handler with the function or fire off notifications within the firebase function.
Update: Apple have implemented updates in regards to background modes which have stopped this method from working . Currently the only method is to use APNS