Stay Connected: Mastering iOS Notifications for Seamless Communication

Photo by Pavan Trikutam / Unsplash

Introduction

In today’s interconnected world, timely and relevant communication is the cornerstone of user engagement in mobile applications. iOS developers have a sophisticated tool at their disposal: communication notifications, designed specifically for chat and other user-to-user interactions. These notifications not only inform users of new messages but also enhance the overall experience by integrating seamlessly with iOS features like Focus status updates. This blog post will guide you through the nuances of setting up and managing these specialized notifications, ensuring your app remains at the forefront of effective user communication. Whether you're building a messaging app or integrating chat features into your service, understanding how to leverage iOS communication notifications is crucial for creating a responsive and user-friendly interface.

Adding a Notification Service Extension to Handle Code Execution

To enhance the functionality of your iOS notifications, particularly for user-to-user communication, it's essential to implement a Notification Service Extension. This extension allows your app to modify the content of push notifications before they are delivered to the user's device, providing a more dynamic and interactive user experience.

Step 1: Setting Up the Notification Service Extension

To begin, you need to add a Notification Service Extension to your iOS project:
- Open your project in Xcode.
- Go to File > New > Target.
- Select Notification Service Extension from the list of templates.
- Name your extension and add it to your app’s target.

Step 2: Configuring the Payload

For the Notification Service Extension to be triggered, the incoming push notifications must include the mutable-content key with a value of 1 in the payload. This key signals iOS that the notification content can be modified by your app before it is shown to the user. Here’s an example of how your notification payload should look:

{
    "aps": {
        "alert": {
            "title": "New Message",
            "body": "You have received a new message from Alice."
        },
        "mutable-content": 1
    }
}

Step 3: Modifying the plist File

After setting up the Notification Service Extension, you need to configure its Info.plist file to ensure proper operation, this should be in your root level dict:

<key>NSExtension</key>
	<dict>
		<key>NSExtensionAttributes</key>
		<dict>
			<key>IntentsSupported</key>
			<array>
				<string>INSendMessageIntent</string>
			</array>
		</dict>
		<key>NSExtensionPointIdentifier</key>
		<string>com.apple.usernotifications.service</string>
		<key>NSExtensionPrincipalClass</key>
		<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict>

Step 4: Enabling Communication Notifications in the Main Project

To utilize communication notifications effectively, you must enable specific capabilities in your main project:
- Open your main project’s target in Xcode.
- Navigate to the Capabilities tab and enable Push Notifications.
- Also, enable Background Modes and check Remote notifications to allow your app to receive notifications in the background.

Further configuration is required in the Info.plist file of your main project to handle user activities, add this entry to your root dict:

<key>NSUserActivityTypes</key>
	<array>
		<string>INSendMessageIntent</string>
	</array>

Step 5: Modifying Notification Content

Within the Notification Service Extension, you can modify the notification’s content by overriding the didReceive(_:withContentHandler:) method. This method is called when a push notification arrives, and it allows you to perform tasks such as downloading additional media, decrypting content, or adding rich media attachments. Here is a basic implementation:

func didReceive(
    request: UNNotificationRequest,
    withContentHandler contentHandler: @escaping (UNNotificationContent
    ) -> Void) {
    let handle = INPersonHandle(value: "unique-user-id)", type: .unknown)
    let sender = INPerson(personHandle: handle,
                                  nameComponents: nil,
                                  displayName: "Name",
                                  image: nil, // here you can provide UIImage for user avatar
                                  contactIdentifier: nil,
                                  customIdentifier: "unique-user-id")
    
    let intent = INSendMessageIntent(
        recipients: nil,
        outgoingMessageType: .outgoingMessageText,
        content: "content of message",
        speakableGroupName: nil,
        conversationIdentifier: "unique-user-id-conv",
        serviceName: nil,
        sender: sender,
        attachments: nil
    )
    
    let interaction = INInteraction(intent: intent, response: nil)
    
    interaction.direction = .incoming
    
    interaction.donate(completion: nil)
    
    let content = request.content
    
    do {
        let updatedContent = try content.updating(from: intent)
        let mutableBestAttemptContent = (updatedContent.mutableCopy() as? UNMutableNotificationContent)!
        mutableBestAttemptContent.threadIdentifier = "Thread-identifier" // use this field for grouping notifications
        mutableBestAttemptContent.userInfo = request.content.userInfo
        
        contentHandler(mutableBestAttemptContent)
        
    } catch {
        // Handle errors that may occur while updating content.
    }
}

Creating an INPerson Instance

INPersonHandle: Initializes with a unique user identifier. This handle is used to create an INPerson object.
INPerson: Represents the sender of the message. It includes optional parameters such as the display name and an image, which could be used to show the sender's avatar.

Setting Up the INSendMessageIntent

INSendMessageIntent: This intent is used to represent an outgoing message. The function sets up this intent with various parameters including the content of the message and a conversation identifier, which is crucial for linking this message to a specific conversation thread.

Donating the Interaction

INInteraction: Represents an interaction involving the intent. The interaction is marked as incoming and is donated to the system. Donating the interaction helps the system to learn and predict the user’s behavior, improving the app's integration with Siri and other predictive features.

Modifying the Notification Content

Updating Content: The original notification content is updated using the intent to include any additional information or modifications specified by the intent.
UNMutableNotificationContent: A mutable copy of the updated content is created to allow further modifications.
Thread Identifier: This is set to group related notifications together, which is particularly useful for message threads.

Error Handling

The function includes error handling to manage any issues that might arise during the content updating process, ensuring that the app can gracefully handle unexpected situations.

Step 6: Testing Your Extension

After implementing the extension, test it by sending a push notification with the mutable-content key set to 1. Ensure that your service extension modifies the notification as expected and that the changes are reflected when the notification is delivered to the device.

Artur Gruchała

Artur Gruchała

I started learning iOS development when Swift was introduced. Since then I've tried Xamarin, Flutter, and React Native. Nothing is better than native code:)
Poland