通信通知 Communication Notifications 的实现 (iOS 15+)

WWDC 2021 苹果在 iOS 15 系统中对通知做了很多改变, 让通知更加个性化.

这里只有讨论通信通知 Communication Notifications, 苹果自带的很多应用, 以及第三方App 飞书, 都使用了这个通知功能。

通信通知 Communication Notifications 简介

iOS 15系统后, Apple 添加了通信通知的功能。这些通知将包含发送它们的联系人的头像,并且可以与 SiriKit 集成,以便 Siri 可以智能地根据常用联系人提供通信操作的快捷方式和建议。

图例:
image


通信通 Communication Notifications 具体实现:

要使用通信通知,App 需要在 Xcode 中将通信通知功能添加到其应用程序,并在应用程序通知服务扩展中实现 UNNotificationContentProviding 协议。

1.首先将以下键值添加到主应用程序 Info.plist 文件中
NSUserActivityTypes (Array)
    - INStartCallIntent
    - INSendMessageIntent

具体位置如图所示:
image

2.在 Xcode -> Capabilities 中添加 Communication Notifications 功能

如图所示:
image

3.添加 Notification Service Extension 扩展

大多数社交媒体通知都是从服务器发送到 Apple 的 APN 服务器,然后再发送到设备。
我们需要使用通知服务扩展, 该扩展用于处理通知,然后将它们显示在屏幕上。

首先,将扩展Notification Service Extension 添加到项目中
请添加图片描述

然后将以下键和值添加到 Notification Service Extension 扩展 Info.plist
请添加图片描述

4.在 Notification Service Extension 扩展下 NotificationService 文件中, 重写 didReceive 方法

Apple APN 服务器每次在通知出现在用户屏幕上之前,都会调用此方法

初始代码如下:

import UIKit
import Intents
import UserNotifications

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        if let bestAttemptContent = bestAttemptContent {
            // ...
        }
    }
}

我们可以通过使用 INPersonINSendMessageIntent创建此信息将其添加到您的推送通知消息中

具体实现代码如下:

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

   override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        
        if let bestAttemptContent = bestAttemptContent {
            // Modify the notification content here...
            // 获取通信消息
            if let senderAccountID = bestAttemptContent.userInfo["sender_id"] as? String,
               // 发送者名称
               let senderName = bestAttemptContent.userInfo["sender_name"] as? String,
               // 发送者图像url地址
               let senderImageURLString = bestAttemptContent.userInfo["sender_image_url"] as? String,
               // 发送者昵称
               let senderDisplayName = bestAttemptContent.userInfo["sender_nickname"] as? String,
               // 通信id
               let chatSessionID = bestAttemptContent.userInfo["chat-session_id"] as? String
            {
                // Here you need to download the image data from the URL.
                self.getMediaAttachment(for: senderImageURLString) { image in
                    guard let groupIcon = image else {
                        contentHandler(bestAttemptContent)
                        return
                    }
                    let avatar = INImage(imageData: groupIcon.pngData()!)
                    // 消息发送方
                    let messageSender = INPerson(
                        personHandle: INPersonHandle(value: nil, type: .unknown),
                        nameComponents: try? PersonNameComponents(senderName),
                        displayName: senderDisplayName,
                        image: avatar,
                        contactIdentifier: nil,
                        customIdentifier: senderAccountID,
                        isMe: false,
                        suggestionType: .none
                    )
                    
                    // 消息接收方
                    let mePerson = INPerson(
                        personHandle: INPersonHandle(value: "", type: .unknown),
                        nameComponents: nil,
                        displayName: nil,
                        image: nil,
                        contactIdentifier: nil,
                        customIdentifier: nil,
                        isMe: true,
                        suggestionType: .none
                    )
                    
                    let intent = INSendMessageIntent(recipients: [mePerson, messageSender],
                                                     outgoingMessageType: .outgoingMessageText,
                                                     content: bestAttemptContent.body,
                                                     speakableGroupName: INSpeakableString(spokenPhrase: senderDisplayName),
                                                     conversationIdentifier: chatSessionID,
                                                     serviceName: nil,
                                                     sender: messageSender,
                                                     attachments: nil)
                    
                    intent.setImage(avatar, forParameterNamed: \.speakableGroupName)

                    let interaction = INInteraction(intent: intent, response: nil)
                    interaction.direction = .incoming
                    interaction.donate(completion: nil)
                    do {
                        let messageContent = try request.content.updating(from: intent)
                        contentHandler(messageContent)
                    } catch {
                        print(error.localizedDescription)
                        contentHandler(bestAttemptContent)
                    }
                }
            }
            
            contentHandler(bestAttemptContent)
        }
    }
    
    override func serviceExtensionTimeWillExpire() {
        // Called just before the extension will be terminated by the system.
        // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
}

使用到的工具扩展, 包含图片下载和本地保存

extension NotificationService {

    // 保存图片到本地 并返回 本地 url 地址
    private func saveImageAttachment(image: UIImage, forIdentifier identifier: String) -> URL? {
        // 1 获取临时文件夹
        let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory())
        // 2 拼接文件路径
        let directoryPath = tempDirectory.appendingPathComponent(
            ProcessInfo.processInfo.globallyUniqueString,
            isDirectory: true)
        
        do {
            // 3 如果文件夹不存在 创建文件夹
            try FileManager.default.createDirectory(
                at: directoryPath,
                withIntermediateDirectories: true,
                attributes: nil)
            
            // 4 文件地址URL
            let fileURL = directoryPath.appendingPathComponent(identifier)
            // 5 文件二进制
            guard let imageData = image.pngData() else {
                return nil
            }
            
            // 6 保存二进制文档到本地路径
            try imageData.write(to: fileURL)
            return fileURL
        } catch {
            return nil
        }
    }
    
    //  通过本地 url 地址 获取图片资源
    private func getMediaAttachment(for urlString: String, completion: @escaping (UIImage?) -> Void) {
        // 1
        guard let url = URL(string: urlString) else {
            completion(nil)
            return
        }
        
        // 2 通过远程图片URL下载图片
        downloadImage(forURL: url) { result in
            // 3
            guard let image = try? result.get() else {
                completion(nil)
                return
            }
            
            // 4
            completion(image)
        }
    }
    
    // 通过远程图片url地址 下载图片文件
    public enum DownloadError: Error {
        case emptyData
        case invalidImage
    }

    private func downloadImage(forURL url: URL, completion: @escaping (Result<UIImage, Error>) -> Void) {
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            guard let data = data else {
                completion(.failure(DownloadError.emptyData))
                return
            }
            
            guard let image = UIImage(data: data) else {
                completion(.failure(DownloadError.invalidImage))
                return
            }
            
            completion(.success(image))
        }
        
        task.resume()
    }
}

额外补充: 通知 Notifications 实现附件图片展示

在回调 bestAttemptContent 之前, 给它设置 attachments属性即可
@available(iOS 10.0, *)
open class UNMutableNotificationContent : UNNotificationContent {
	/// ...
  	// Optional array of attachments.
    open var attachments: [UNNotificationAttachment]
	/// ...
}

核心代码如下:

// 创建图片附件
let imageAttachment = try? UNNotificationAttachment(
    identifier: "image",
    url: "图片本地路径, 和上面设置 Avatar 地址一样",
    options: nil)

// 赋值给 bestAttemptContent 
if let imageAttachment = imageAttachment {
    bestAttemptContent.attachments = [imageAttachment]
}

// 回调出去
contentHandler(bestAttemptContent)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Django中使用WebSocket实现系统消息通知可以通过以下步骤实现: 1. 安装Django Channels和asgiref ```bash pip install channels asgiref ``` 2. 创建一个Django应用程序 ```bash python manage.py startapp notifications ``` 3. 创建一个WebSocket路由 在`notifications`应用程序中创建一个`routing.py`文件,添加以下内容: ```python from django.urls import re_path from . import consumers websocket_urlpatterns = [ re_path(r'ws/notifications/$', consumers.NotificationConsumer.as_asgi()), ] ``` 4. 创建一个WebSocket消费者 在`notifications`应用程序中创建一个`consumers.py`文件,添加以下内容: ```python import asyncio import json from channels.consumer import AsyncConsumer from channels.db import database_sync_to_async from django.contrib.auth.models import User class NotificationConsumer(AsyncConsumer): async def websocket_connect(self, event): await self.send({ "type": "websocket.accept" }) user = self.scope["user"] if user.is_authenticated: await self.channel_layer.group_add( f"user_{user.id}", self.channel_name ) async def websocket_receive(self, event): user = self.scope["user"] if user.is_authenticated: data = json.loads(event["text"]) message = data["message"] await self.create_message(user, message) await self.channel_layer.group_send( f"user_{user.id}", { "type": "user.message", "message": message } ) async def websocket_disconnect(self, event): user = self.scope["user"] if user.is_authenticated: await self.channel_layer.group_discard( f"user_{user.id}", self.channel_name ) @database_sync_to_async def create_message(self, user, message): user = User.objects.get(id=user.id) user.notifications.create(message=message) ``` 5. 配置WebSocket路由 在`settings.py`文件中添加以下内容: ```python ASGI_APPLICATION = 'project_name.routing.application' CHANNEL_LAYERS = { "default": { "BACKEND": "channels.layers.InMemoryChannelLayer" } } ROOT_URLCONF = 'project_name.urls' INSTALLED_APPS = [ ... 'channels', 'notifications', ] ``` 6. 创建一个JavaScript文件 在`static/js/notifications.js`文件中添加以下内容: ```javascript var socket = new WebSocket("ws://" + window.location.host + "/ws/notifications/"); socket.onmessage = function(event) { var message = JSON.parse(event.data)["message"]; alert(message); } ``` 7. 在模板中引入JavaScript文件 在需要使用WebSocket的模板中添加以下内容: ```html {% load static %} <script src="{% static 'js/notifications.js' %}"></script> ``` 现在,当用户登录并连接到WebSocket时,他们将加入名为`user_<user_id>`的组。当用户收到新消息时,消息将保存到数据库中,并通过WebSocket发送到所有连接到该组的用户。在前端,我们使用JavaScript来处理接收到的消息,这里简单地使用了一个警报框来显示消息,你可以改为使用其他的UI库。 希望这个教程能够帮助你实现Django和WebSocket的消息通知功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值