📦 SwiftyLaunch Modules
🔔 NotifKit
Routing to In-App Notifications

Routing Push Notifications to In-App Notifications

NotifKit (SwiftyLaunch Module) - 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 or compact. Defaults to normal if no value is passed.
  • inAppHaptics (Optional): Haptic feedback when the in-app notification is shown, either success, warning or error. Defaults to warning 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.

OneSignal Dashboard - 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.

PushNotifications.ts
export async function sendNotificationToUserWithID({
  userID,
  data,
}: {
  userID: string;
  data: NotificationReadableData;
}) {}
PushNotifications.ts
type NotificationReadableData = {
  title: string;
  message: string;
  additionalData?: InAppNotificationAdditionalData;
};
PushNotifications.ts
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.

App.swift
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))
 
	}
    // ...
}