SwiftUI 2 Firebase push notification
I was able to get your code to work. I think that Firebase has an issue with swizzling the AppDelegate
.
If you disable swizzling by adding the key FirebaseAppDelegateProxyEnabled
to your Info.plist
, making it a Boolean
and setting it to NO
.
Then in your AppDelegate
you need to add the following, so that it registers the APNS token with Firebase.:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
For Firebase Cloud Messaging to work it needs to make a link between the APNS token and its token. If no link is made then you will not be able to send messages.
Note that by disabling swizzling there will be certain properties that
you will have to update manually.
You should read the documentation carefully. At the time of writing this answer there are several places that you need to update if you disable swizzling.
There are three in the AppDelegate. The first is shown above, the other two are here and are to do with receiving background messages.
You are adding the following line to the delegate methods:
Messaging.messaging().appDidReceiveMessage(userInfo)
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
Messaging.messaging().appDidReceiveMessage(userInfo) // <- this line needs to be uncommented
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
Messaging.messaging().appDidReceiveMessage(userInfo) // <- this line needs to be uncommented
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
There are two in the UNUserNotificationCenterDelegate. Where you add the follow line to the delegate methods
Messaging.messaging().appDidReceiveMessage(userInfo)
extension AppDelegate : UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
// With swizzling disabled you must let Messaging know about the message, for Analytics
Messaging.messaging().appDidReceiveMessage(userInfo) // <- this line needs to be uncommented
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
// Change this to your preferred presentation option
completionHandler([[.alert, .sound]])
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// With swizzling disabled you must let Messaging know about the message, for Analytics
Messaging.messaging().appDidReceiveMessage(userInfo) // <- this line needs to be uncommented
// Print full message.
print(userInfo)
completionHandler()
}
}
This above code is lifted from Firebase's Cloud Messaging documentation. They are very clear in the comments of their code what needs to be done if you have disabled swizzling.
Depending on which other Firebase modules you may include in your project you may have other values you need to update manually. This is where you will need to check carefully with the documentation and code samples to see what changes you have to make.
SwiftUI remote push notifications without AppDelegate (Firebase Cloud Messaging)
I just created an App Delegate
. Works for local and remote notifications.
I have a PushNotificationManager
that does the remote pushing. Whenever I send data to Firebase (I'm using Firestore), I pass in the AppDelegate.fcmToken
to the user's fcmToken property (every user has one in the model) e.g. token: user.fcmToken
.
class AppDelegate: NSObject, UIApplicationDelegate {
private var gcmMessageIDKey = "gcm_message_idKey"
static var fcmToken = String()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
Messaging.messaging().delegate = self
UNUserNotificationCenter.current().delegate = self
registerForPushNotifications()
return true
}
func registerForPushNotifications() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
print("Permission granted: \(granted)")
guard granted else { return }
self?.getNotificationSettings()
}
}
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
AppDelegate.fcmToken = deviceToken.hexString
}
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("Failed to register: \(error.localizedDescription)")
}
func application(
_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
print(userInfo)
completionHandler(.newData)
}
}
Extensions
@available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
print("Will Present User Info: \(userInfo)")
completionHandler([[.banner, .sound]])
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if response.actionIdentifier == "accept" {
print("Did Receive User Info: \(userInfo)")
completionHandler()
}
}
}
extension AppDelegate: MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
let dataDict: [String: String] = [AppDelegate.fcmToken: fcmToken ?? ""]
NotificationCenter.default.post(name: NSNotification.Name("FCMToken"), object: nil, userInfo: dataDict)
// Note: This callback is fired at each app startup and whenever a new token is generated.
AppDelegate.fcmToken = fcmToken!
}
}
extension Data {
var hexString: String {
let hexString = map { String(format: "%02.2hhx", $0) }.joined()
return hexString
}
}
SwiftUI handle push notifications tapped in a terminated state
Okay, I found the same problem in Swift, and the solution was to just delay the action of the notification tapped with one second. It feels more like a hack, I want to know exactly what happens asynchronously, but it works perfectly.
Here is the solution:
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { // <- here!!
self.notificationManager?.pageToNavigationTo = 1
self.notificationManager?.recievedNotificationFromChatID = chatID
}
Swiftui: How can I navigate to a specific NavigationLink when user clicks on push notification?
Assuming this callback is in AppDelegate, you can make AppDelegate owner of your shared object which is used as environment object, so you have access to it and in app delegate callbacks and in scene delegate to inject into content view.
Please see my proposed approach for similar scenario in App Delegate Accessing Environment Object
SwiftUI ask Push Notifications Permissions again
The short answer is no, you can't ask the user again if he once disabled the push-notifications for your app.
What you can do, is navigating the user to the settings in their phone to allow push-notifications again.
The code snippet in SwiftUI for the button would be:
Button(action: {
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}, label: {
Text("Allow Push")
})
I would also refer to this question: How to ask notifications permissions if denied?
Related Topics
When Should I Use Optionals and When Should I Use Non-Optionals with Default Values
Realm Mobile Platform, How to Connect While Offline
Sending a Parameter Argument to Function Through Uitapgesturerecognizer Selector
How to Determine If a Variable Passed in Is Reference Type or Value Type
How to Make Firebase Database Data the Data Source for Uicollection View
Scenekit - Animation with Dae File Format
Using Nstimer in Swift Playground
Getting Unresolved Identifier 'Self' in Swiftui Code Trying to Use Timer.Scheduledtimer
Ycombinator Not Working in Swift
Display All Available Wifi Connections with Swift in Os X
How to Open/View iOS Oslogs Stored on Device
How to Create a Noop Block for a Switch Case in Swift
How to Convert Int32 to Int in Swift