Routing Push Notifications to In-App Notifications
SwiftyLaunch includes a convenient way to show alerts, errors and other attention-grabbing messages to the user via SharedKit's In-App Notifications.
So we thought that it would be a nice touch if we could hide push notifications when they arrive and show them as in-app notifications instead if the app is open.
Basic Usage
In order for your SwiftyLaunch app to have the ability show a push notification as an in-app notification,
you need to pass additional parameters to your notification payload. These parameters will used to create an InAppNotificationStyle
object
that will be used to style the in-app notification.
Parameters
inAppSymbol
: SF Symbol of the notification. Refer to the SF Symbols App (opens in a new tab) for the list of available symbols. Example:umbrella.fill
inAppColor
: Color of the in-app notification symbol in HEX format. Example:#3774E6
inAppSize
(Optional): Size of the in-app notification symbol,normal
orcompact
. Defaults tonormal
if no value is passed.inAppHaptics
(Optional): Haptic feedback when the in-app notification is shown, eithersuccess
,warning
orerror
. Defaults towarning
if no value is passed.
Note that you also must pass both a title and a message in the push notification payload to be able for it to be shown as an in-app notification.
Example 1: Via the OneSignal Dashboard
When sending a push notification via the OneSignal dashboard, you can add the parameters to the Additional Data
field.
Example 2: Via the BackendKit function
If you send a push notification via the NotifKit's sendNotificationToUserWithID()
function in BackendKit,
you can simply pass the additional in-app notification data in the optional additionalData (InAppNotificationAdditionalData
)
parameter of NotificationReadableData
.
export async function sendNotificationToUserWithID({
userID,
data,
}: {
userID: string;
data: NotificationReadableData;
}) {}
type NotificationReadableData = {
title: string;
message: string;
additionalData?: InAppNotificationAdditionalData;
};
type InAppNotificationAdditionalData = {
inAppSymbol: SFSymbol;
inAppColor: string;
inAppSize?: "normal" | "compact";
inAppHaptics?: "success" | "warning" | "error";
};
The SFSymbol is a string union type (opens in a new tab) that can be any of the SF Symbols available in the SF Symbols app. (Up to SF Symbols 3)
Here's an example of using it:
import * as PushNotifications from "./NotifKit/PushNotifications";
export const sendNotificationTo = onCall(async (request) => {
let fromUid = request.auth?.uid;
// ...
try {
if (!fromUid) {
throw new Error("User Not Logged In");
}
const user = await getAuth().getUser(fromUid);
let toUid = request.data?.userID as string;
if (!toUid) {
// ...
throw new Error("No Receiver UserID provided");
}
let message = request.data?.message as string | "Empty Message";
await PushNotifications.sendNotificationToUserWithID({
userID: toUid,
data: {
title: `Message from ${user.displayName || fromUid}`,
message: message,
additionalData: {
inAppSymbol: "bolt.fill",
inAppColor: "#ae0000",
inAppSize: "compact",
inAppHaptics: "error",
},
},
});
// ...
} catch (error) {
// ...
throw new Error("Server Error");
}
return true;
});
Deep Dive
Now how do we do it? Pretty simple actually. We set the AppDelegate
in App.swift
to be a delegate of OSNotificationLifecycleListener
and attach a Foreground
Lifecycle Listener (OneSignal.Notifications.addForegroundLifecycleListener(self)
) during app initialization.
This Lifecycle Listener will call the onWillDisplay
function when a notification is about to be displayed.
If the notification payload includes the required parameters, we prevent the notification from being displayed and instead show an in-app notification. Otherwise, we just show the notification as usual.
class AppDelegate: NSObject, UIApplicationDelegate, OSNotificationLifecycleListener, /* ... */ {
// ...
func onWillDisplay(event: OSNotificationWillDisplayEvent) {
event.preventDefault()
// If we can't find these properties in additional data, just show notification as usual
guard let notifTitle = event.notification.title,
let notifMessage = event.notification.body,
let additionalData = event.notification.additionalData,
let symbol = additionalData["inAppSymbol"] as? String,
let color = additionalData["inAppColor"] as? String
else {
event.notification.display() // Show notification as usual
return
}
// optionally you can pass notifSize as a parameter
var notifSize: InAppNotificationStyle.NotificationSize = .normal
if let size = additionalData["inAppSize"] as? String {
if size == "compact" {
notifSize = .compact
}
}
// optionally you can pass notifHaptics as a parameter
var notifHaptics: UINotificationFeedbackGenerator.FeedbackType = .warning
if let size = additionalData["inAppHaptics"] as? String {
if size == "error" {
notifHaptics = .error
} else if size == "success" {
notifHaptics = .success
}
}
showInAppNotification(
content: .init(
title: LocalizedStringKey(notifTitle),
message: LocalizedStringKey(notifMessage)),
style: .init(
sfSymbol: symbol,
symbolColor: Color(hex: color),
size: notifSize,
hapticsOnAppear: notifHaptics))
}
// ...
}