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

Posted iMazy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通信通知 Communication Notifications 的实现 (iOS 15+)相关的知识,希望对你有一定的参考价值。

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

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

通信通知 Communication Notifications 简介

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

图例:


通信通 Communication Notifications 具体实现:

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

1.首先将以下键值添加到主应用程序 Info.plist 文件中

NSUserActivityTypes (Array)
    - INStartCallIntent
    - INSendMessageIntent

具体位置如图所示:

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

如图所示:

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)

以上是关于通信通知 Communication Notifications 的实现 (iOS 15+)的主要内容,如果未能解决你的问题,请参考以下文章

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

操作系统之三Linux下进程间通信-IPC(Inter-Process Communication)

网络通信中的单工(Simplex Communication)半双工(Half-duplex Communication)全双工(Full-duplex Communication)是什么意思?

网络通信中的单工(Simplex Communication)半双工(Half-duplex Communication)全双工(Full-duplex Communication)是什么意思?

interprocess communication (IPC) 进程间通信

python - serial communication(串口通信)