`
啸笑天
  • 浏览: 3465235 次
  • 性别: Icon_minigender_1
  • 来自: China
社区版块
存档分类
最新评论

iOS10 推送通知整理

 
阅读更多

 

 

iOS 10使用独立的 UserNotifications.framework 来集中管理和使用 iOS 系统中通知的功能。在此基础上,Apple 还增加了撤回单条通知,更新已展示通知,中途修改通知内容,在通知中展示图片视频,自定义通知 UI 等一系列新功能,非常强大。

WWDC 视频:

https://developer.apple.com/videos/play/wwdc2016/707/

https://developer.apple.com/videos/play/wwdc2016/708/

官方文档:

https://developer.apple.com/reference/usernotifications

 

历史:

从在 iOS 3 引入 Push Notification 后,之后几乎每个版本 Apple 都在加强这方面的功能。我们可以回顾一下整个历程和相关的主要 API:

iOS 3 - 引入推送通知 UIApplication 的 registerForRemoteNotificationTypes 与UIApplicationDelegate 的application(_:didRegisterForRemoteNotificationsWithDeviceToken:),application(_:didReceiveRemoteNotification:)

iOS 4 - 引入本地通知scheduleLocalNotification,presentLocalNotificationNow:,application(_:didReceive:)

iOS 5 - 加入通知中心页面

iOS 6 - 通知中心页面与 iCloud 同步

iOS 7 - 后台静默推送application(_:didReceiveRemoteNotification:fetchCompletionHandle:)

iOS 8 - 重新设计 notification 权限请求,Actionable 通知registerUserNotificationSettings(_:),UIUserNotificationAction 与UIUserNotificationCategory,application(_:handleActionWithIdentifier:forRemoteNotification:completionHandler:) 等。

在iOS 8 中,我们可以给推送增加用户操作,这样使推送更加具有交互性,并且允许用户去处理用户推送更加的迅速。

iOS 9 - Text Input action,基于 HTTP/2 的推送请求UIUserNotificationActionBehavior,全新的 Provider API 等

到了iOS 9 中,苹果又再次增加了快速回复功能,进一步的提高了通知的响应性。开发者可以允许用户通过点击推送,并用文字进行回复。

通知功能相对还是简单,我们能做的只是本地或者远程发起通知,然后显示给用户。虽然 iOS 8 和 9 中添加了按钮和文本来进行交互,但是已发出的通知不能更新,通知的内容也只是在发起时唯一确定,而这些内容也只能是简单的文本。 想要在现有基础上扩展通知的功能,势必会让原本就盘根错节的 API 更加难以理解。

 

权限申请,注册远程通知

 

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge, .carPlay]) {//申请权限
            granted, error in
            if granted {
                UIApplication.shared.registerForRemoteNotifications()//用户同意后可注册远程通知 ,来获得token
            } else {
                if let error = error {
                    UIAlertController.showConfirmAlert(message: error.localizedDescription, in: self)
                }
            }
        }

 注册token后的回调(在新notification架构中没有变的两个接口)

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let tokenString = deviceToken.hexString
        UserDefaults.standard.set(tokenString, forKey: "push-token")
        NotificationCenter.default.post(name: .AppDidReceivedRemoteNotificationDeviceToken, object: nil, userInfo: [Notification.Key.AppDidReceivedRemoteNotificationDeviceTokenKey: tokenString])
        
        print("Get Push token: \(tokenString)")
    }
    
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        UserDefaults.standard.set("", forKey: "push-token")
    }


extension Data {
    var hexString: String {
        return withUnsafeBytes {(bytes: UnsafePointer<UInt8>) -> String in
            let buffer = UnsafeBufferPointer(start: bytes, count: count)
            return buffer.map {String(format: "%02hhx", $0)}.reduce("", { $0 + $1 })
        }
    }
}

 

权限检查:

用户可以在系统设置中修改你的应用的通知权限,除了打开和关闭全部通知权限外,用户也可以限制你的应用只能进行某种形式的通知显示,比如只允许横幅而不允许弹窗及通知中心显示等。一般来说你不应该对用户的选择进行干涉,但是如果你的应用确实需要某种特定场景的推送的话,你可以对当前用户进行的设置进行检查:

UNUserNotificationCenter.current().getNotificationSettings {
    settings in 
    print(settings.authorizationStatus) // .authorized | .denied | .notDetermined
    print(settings.badgeSetting) // .enabled | .disabled | .notSupported
    // etc...
}

 

 发送通知:

本地: 

UserNotifications 中对通知进行了统一。我们通过通知的内容 (UNNotificationContent),发送的时机 (UNNotificationTrigger) 以及一个发送通知的 String 类型的标识符,来生成一个UNNotificationRequest 类型的发送请求。最后,我们将这个请求添加到UNUserNotificationCenter.current() 中,就可以等待通知到达了:

// 1. 创建通知内容
let content = UNMutableNotificationContent()
content.title = "Time Interval Notification"
content.body = "My first notification"

// 2. 创建发送触发
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)

// 3. 发送请求标识符
let requestIdentifier = "com.onevcat.usernotification.myFirstNotification"

// 4. 创建一个发送请求
let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)

// 将请求添加到发送中心
UNUserNotificationCenter.current().add(request) { error in
    if error == nil {
        print("Time Interval Notification scheduled: \(requestIdentifier)")
    }  else {
                print("Notification request added: \(identifier)")
     }
}

 2、触发器可以区分本地和远程通知,本地通知提供的三个触发器:

在一定时间后触发UNTimeIntervalNotificationTrigger,

在某月某日某时触发UNCalendarNotificationTrigger 

在用户进入或是离开某个区域时触发UNLocationNotificationTrigger。

 

远程推送的通知的话默认会在收到后立即显示,远程通知触发器:UNPushNotificationTrigger 

 

3、请求标识符可以用来区分不同的通知请求,在将一个通知请求提交后,通过特定 API 我们能够使用这个标识符来取消或者更新这个通知

 

4、在新版本的通知框架中,Apple 借用了一部分网络请求的概念。我们组织并发送一个通知请求,然后将这个请求提交给 UNUserNotificationCenter 进行处理。我们会在 delegate 中接收到这个通知请求对应的 response,另外我们也有机会在应用的 extension 中对 request 进行处理。

 

 取消和更新

在创建通知请求时,我们已经指定了标识符。这个标识符可以用来管理通知。在 iOS 10 之前,我们很难取消掉某一个特定的通知,也不能主动移除或者更新已经展示的通知。想象一下你需要推送用户账户内的余额变化情况,多次的余额增减或者变化很容易让用户十分困惑 - 到底哪条通知才是最正确的?又或者在推送一场比赛的比分时,频繁的通知必然导致用户通知中心数量爆炸,而大部分中途的比分对于用户来说只是噪音。

iOS 10 中,UserNotifications 框架提供了一系列管理通知的 API,你可以做到:

 

更新通知:

 

本地通知

* 更新还未展示或已经展示过的通知

     和一开始添加请求时一样,再次将请求提交给UNUserNotificationCenter

远程通知

在使用 Provider API 向 APNs 提交请求时,在 HTTP/2 的 header 中apns-collapse-id key 的内容将被作为该推送的标识符进行使用。多次推送同一标识符的通知即可进行更新。

 

 

取消通知:

 

本地通知

* 取消还未展示的通知

    open func removePendingNotificationRequests(withIdentifiers identifiers: [String])

    open func removeAllPendingNotificationRequests()

* 取消已经展示过的通知

    open func removeDeliveredNotifications(withIdentifiers identifiers: [String])

    open func removeAllDeliveredNotifications()

远程通知

对应本地的 removeDeliveredNotifications,现在还不能通过类似的方式,向 APNs 发送一个包含 collapse id 的 DELETE 请求来删除已经展示的推送,APNs 服务器并不接受一个 DELETE 请求。不过从技术上来说 Apple 方面应该不存在什么问题,我们可以拭目以待。现在如果想要消除一个远程推送,可以选择使用后台静默推送的方式来从本地发起一个删除通知的调用。

 

 处理通知

 UNUserNotificationCenterDelegate提供了两个方法:

 

@available(iOS 10.0, *)
public protocol UNUserNotificationCenterDelegate : NSObjectProtocol {

    //如何在应用内展示通知
    // The method will be called on the delegate only if the application is in the foreground. If the method is not implemented or the handler is not called in a timely manner then the notification will not be presented. The application can choose to have the notification presented as a sound, badge, alert and/or in the notification list. This decision should be based on whether the information in the notification is otherwise visible to the user.
    @available(iOS 10.0, *)
    optional public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Swift.Void)

    //收到通知响应时要如何处理的工作
    // The method will be called on the delegate when the user responded to the notification by opening the application, dismissing the notification or choosing a UNNotificationAction. The delegate must be set before the application returns from applicationDidFinishLaunching:.
    @available(iOS 10.0, *)
    optional public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Swift.Void)
}
  • 应用内展示通知:

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {

       // 可以根据notification.request.identifier的值做出不同业务的反应:

      completionHandler([.alert, .sound])

      // 如果不想显示某个通知,可以直接用空 options 调用 completionHandler:

        // completionHandler([])

    }

 

  •  对通知进行响应

userNotificationCenter(_:didReceive:withCompletionHandler:)。这个代理方法会在用户与你推送的通知进行交互时被调用,包括用户通过通知打开了你的应用,或者点击或者触发了某个 action。因为涉及到打开应用的行为,所以实现了这个方法的 delegate 必须在applicationDidFinishLaunching: 返回前就完成设置:

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

   /*

   进行自己的业务操作,UNNotificationResponse 是一个几乎包括了通知的所有信息的对象

   */

    completionHandler()

}

 

  •  Actionable 通知发送和处理



 

 1、注册 Category:

 iOS 8 和 9 中 Apple 引入了可以交互的通知,这是通过将一簇 action 放到一个 category 中,将这个 category 进行注册,最后在发送通知时将通知的 category 设置为要使用的 category 来实现的。

 

private func registerNotificationCategory() {
        
        let saySomethingCategory: UNNotificationCategory = {
            
            let inputAction = UNTextInputNotificationAction(
                identifier: SaySomethingCategoryAction.input.rawValue,
                title: "Input",
                options: [.foreground],
                textInputButtonTitle: "Send",
                textInputPlaceholder: "What do you want to say...")
            
            let goodbyeAction = UNNotificationAction(
                identifier: SaySomethingCategoryAction.goodbye.rawValue,
                title: "Goodbye",
                options: [.foreground])
            
            let cancelAction = UNNotificationAction(
                identifier: SaySomethingCategoryAction.none.rawValue,
                title: "Cancel",
                options: [.destructive])
        
            return UNNotificationCategory(identifier: UserNotificationCategoryType.saySomething.rawValue, actions: [inputAction, goodbyeAction, cancelAction], intentIdentifiers: [], options: [.customDismissAction])
        }()
        
        let customUICategory: UNNotificationCategory = {
            let nextAction = UNNotificationAction(
                identifier: CustomizeUICategoryAction.switch.rawValue,
                title: "Switch",
                options: [])
            let openAction = UNNotificationAction(
                identifier: CustomizeUICategoryAction.open.rawValue,
                title: "Open",
                options: [.foreground])
            let dismissAction = UNNotificationAction(
                identifier: CustomizeUICategoryAction.dismiss.rawValue,
                title: "Dismiss",
                options: [.destructive])
            return UNNotificationCategory(identifier: UserNotificationCategoryType.customUI.rawValue, actions: [nextAction, openAction, dismissAction], intentIdentifiers: [], options: [])
        }()
        
        UNUserNotificationCenter.current().setNotificationCategories([saySomethingCategory, customUICategory])
    }

 1. UNTextInputNotificationAction 代表一个输入文本的 action,你可以自定义框的按钮 title 和 placeholder。你稍后会使用 identifier 来对 action 进行区分。

2. 普通的 UNNotificationAction 对应标准的按钮。

3. 为 category 指定一个 identifier,我们将在实际发送通知的时候用这个标识符进行设置,这样系统就知道这个通知对应哪个 category 了。

本地发送带action通知:

在完成 category 注册后,发送一个 actionable 通知就非常简单了,只需要在创建UNNotificationContent 时把 categoryIdentifier 设置为需要的 category id 即可:

content.categoryIdentifier = "saySomethingCategory"

 

2、远程发送带action通知:

只需要在 payload 中添加 category 字段,并指定预先定义的 category id 就可以了:

{

  "aps":{

    "alert":"Please say something",

    "category":"saySomething"

  }

}

 

3、处理action通知:

和普通的通知并无二致,actionable 通知也会走到 didReceive 的 delegate 方法,我们通过 request 中包含的 categoryIdentifier 和 response 里的 actionIdentifier 就可以轻易判定是哪个通知的哪个操作被执行了。对于 UNTextInputNotificationAction 触发的 response,直接将它转换为一个 UNTextInputNotificationResponse,就可以拿到其中的用户输入了:


func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

  // response.notification.request.content.categoryIdentifier
    completionHandler()
}

 

在通知中展示图片/视频

相比于旧版本的通知,iOS 10 中另一个亮眼功能是多媒体的推送。开发者现在可以在通知中嵌入图片或者视频,这极大丰富了推送内容的可读性和趣味性。

本地通知:

为本地通知添加多媒体内容十分简单,只需要通过本地磁盘上的文件 URL 创建一个UNNotificationAttachment 对象,然后将这个对象放到数组中赋值给 content 的 attachments属性就行了:

 

let content = UNMutableNotificationContent()
content.title = "Image Notification"
content.body = "Show me an image!"

if let imageURL = Bundle.main.url(forResource: "image", withExtension: "jpg"),
   let attachment = try? UNNotificationAttachment(identifier: "imageAttachment", url: imageURL, options: nil)
{
    content.attachments = [attachment]
}

 在显示时,横幅或者弹窗将附带设置的图片,使用 3D Touch pop 通知或者下拉通知显示详细内容时,图片也会被放大展示。

除了图片以外,通知还支持音频以及视频。你可以将 MP3 或者 MP4 这样的文件提供给系统来在通知中进行展示和播放。不过,这些文件都有尺寸的限制。关于支持的文件格式和尺寸,可以在https://developer.apple.com/reference/usernotifications/unnotificationattachment中进行确认(系统会自动提供一套可自定义化的UI,专门针对这3种内容。)。在创建 UNNotificationAttachment 时,如果遇到了不支持的格式,SDK 也会抛出错误。

 

 

远程通知:

 通过远程推送的方式,你也可以显示图片等多媒体内容。通过 Notification Service Extension 来修改推送通知内容的技术。一般做法是,我们在推送的 payload 中指定需要加载的图片资源地址,这个地址可以是应用 bundle 内已经存在的资源,也可以是网络的资源。不过因为在创建 UNNotificationAttachment 时我们只能使用本地资源,所以如果多媒体还不在本地的话,我们需要先将其下载到本地。在完成 UNNotificationAttachment 创建后,我们就可以和本地通知一样,将它设置给 attachments 属性,然后调用 contentHandler 了。

简单的示例 payload 如下:

{
  "aps":{
    "alert":{
      "title":"Image Notification",
      "body":"Show me an image from web!"
    },
    "mutable-content":1
  },
  "image": "https://onevcat.com/assets/images/background-cover.jpg"
}

 mutable-content 表示我们会在接收到通知时对内容进行更改,image 指明了目标图片的地址。

在 NotificationService 里,加入如下代码来下载图片,并将其保存到磁盘缓存中:

private func downloadAndSave(url: URL, handler: @escaping (_ localURL: URL?) -> Void) {
    let task = URLSession.shared.dataTask(with: url, completionHandler: {
        data, res, error in

        var localURL: URL? = nil

        if let data = data {
            let ext = (url.absoluteString as NSString).pathExtension
            let cacheURL = URL(fileURLWithPath: FileManager.default.cachesDirectory)
            let url = cacheURL.appendingPathComponent(url.absoluteString.md5).appendingPathExtension(ext)

            if let _ = try? data.write(to: url) {
                localURL = url
            }
        }

        handler(localURL)
    })

    task.resume()
}

 然后在 didReceive: 中,接收到这类通知时提取图片地址,下载,并生成 attachment,进行通知展示:

if let imageURLString = bestAttemptContent.userInfo["image"] as? String,
   let URL = URL(string: imageURLString)
{
    downloadAndSave(url: URL) { localURL in
        if let localURL = localURL {
            do {
                let attachment = try UNNotificationAttachment(identifier: "image_downloaded", url: localURL, options: nil)
                bestAttemptContent.attachments = [attachment]
            } catch {
                print(error)
            }
        }
        contentHandler(bestAttemptContent)
    }
}

 

关于在通知中展示图片或者视频,有几点想补充说明:

* UNNotificationContent 的 attachments 虽然是一个数组,但是系统只会展示第一个 attachment 对象的内容。不过你依然可以发送多个 attachments,然后在要展示的时候再重新安排它们的顺序,以显示最符合情景的图片或者视频。另外,你也可能会在自定义通知展示 UI 时用到多个 attachment(Content Extension)。

* 在当前 beta (iOS 10 beta 4) 中,serviceExtensionTimeWillExpire 被调用之前,你有 30 秒时间来处理和更改通知内容。对于一般的图片来说,这个时间是足够的。但是如果你推送的是体积较大的视频内容,用户又恰巧处在糟糕的网络环境的话,很有可能无法及时下载完成。

* 如果你想在远程推送来的通知中显示应用 bundle 内的资源的话,要注意 extension 的 bundle 和 app main bundle 并不是一回事儿。你可以选择将图片资源放到 extension bundle 中,也可以选择放在 main bundle 里。总之,你需要保证能够获取到正确的,并且你具有读取权限的 url。关于从 extension 中访问 main bundle,可以参看http://stackoverflow.com/questions/26189060/get-the-main-app-bundle-from-within-extension。

The +mainBundle method returns the bundle containing the "current application executable", which is a subfolder of your app when called from within an extension.
The solution that I've found involved peeling off two directory levels from the URL of the bundle, when it ends in "appex".
Objective-C	NSBundle *bundle = [NSBundle mainBundle];
if ([[bundle.bundleURL pathExtension] isEqualToString:@"appex"]) {
    // Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
    bundle = [NSBundle bundleWithURL:[[bundle.bundleURL URLByDeletingLastPathComponent] URLByDeletingLastPathComponent]];
}

NSString *appDisplayName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];	Swift 2.2	var bundle = NSBundle.mainBundle()
if bundle.bundleURL.pathExtension == "appex" {
    // Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
    bundle = NSBundle(URL: bundle.bundleURL.URLByDeletingLastPathComponent!.URLByDeletingLastPathComponent!)!
}

let appDisplayName = bundle.objectForInfoDictionaryKey("CFBundleDisplayName")	Swift 3	var bundle = Bundle.main()
if bundle.bundleURL.pathExtension == "appex" {
    // Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
    do {
        try bundle = Bundle(url: bundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent())!
    } catch {
        print("Could not delete lastPathComponent")
    }
}

let appDisplayName = bundle.objectForInfoDictionaryKey("CFBundleDisplayName")	This will break if the pathExtension or the directory structure for an iOS extension ever changes.

 * 系统在创建 attachement 时会根据提供的 url 后缀确定文件类型,如果没有后缀,或者后缀无法不正确的话,你可以在创建时通过 UNNotificationAttachmentOptionsTypeHintKey 来指定资源类型(https://developer.apple.com/reference/usernotifications/unnotificationattachmentoptionstypehintkey)。

* 如果使用的图片和视频文件不在你的 bundle 内部,它们将被移动到系统的负责通知的文件夹下,然后在当通知被移除后删除。如果媒体文件在 bundle 内部,它们将被复制到通知文件夹下。每个应用能使用的媒体文件的文件大小总和是有限制,超过限制后创建 attachment 时将抛出异常。可能的所有错误可以在 UNError 中找到。

* 你可以访问一个已经创建的 attachment 的内容,但是要注意权限问题。attachment是由系统管理的,系统会把它们单独的管理,这意味着它们存储在我们sandbox之外。所以这里我们要使用attachment之前,我们需要告诉iOS系统,我们需要使用它,并且在使用完毕之后告诉系统我们使用完毕了。对应上述代码就是startAccessingSecurityScopedResource()和stopAccessingSecurityScopedResource()的操作。比如

let content = notification.request.content
if let attachment = content.attachments.first {  
    if attachment.url.startAccessingSecurityScopedResource() {  
        eventImage.image = UIImage(contentsOfFile: attachment.url.path!)  
        attachment.url.stopAccessingSecurityScopedResource()  
    }  
}  

 

Notification Extension

iOS 10 中添加了很多 extension,作为应用与系统整合的入口。与通知相关的 extension 有两个:

Service Extension :可以让我们有机会在收到远程推送的通知后,展示之前对通知内容进行修改

Content Extension:可以用来自定义通知视图的样式。

 

Service Extension :

例如上面远程收到图片url:

import UserNotifications

class NotificationService: UNNotificationServiceExtension {

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

    private func downloadAndSave(url: URL, handler: @escaping (_ localURL: URL?) -> Void) {
        let task = URLSession.shared.dataTask(with: url, completionHandler: {
            data, res, error in
            
            var localURL: URL? = nil
            
            if let data = data {
                let ext = (url.absoluteString as NSString).pathExtension
                let cacheURL = URL(fileURLWithPath: FileManager.default.cachesDirectory)
                let url = cacheURL.appendingPathComponent(url.absoluteString.md5).appendingPathExtension(ext)

                if let _ = try? data.write(to: url) {
                    localURL = url
                }
            }
            
            handler(localURL)
        })
        
        task.resume()
    }
    
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        
        if let bestAttemptContent = bestAttemptContent {
            
            if request.identifier == "mutableContent" {
                
                bestAttemptContent.body = "\(bestAttemptContent.body), onevcat"
                contentHandler(bestAttemptContent)
                
            } else if request.identifier == "media" {
                
                if let imageURLString = bestAttemptContent.userInfo["image"] as? String,
                   let URL = URL(string: imageURLString)
                {
                    downloadAndSave(url: URL) { localURL in
                        if let localURL = localURL {
                            do {
                                let attachment = try UNNotificationAttachment(identifier: "image_downloaded", url: localURL, options: nil)
                                bestAttemptContent.attachments = [attachment]
                            } catch {
                                print(error)
                            }
                        }
                        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 FileManager {
    var cachesDirectory: String {
        var paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true) as [String]
        return paths[0]
    }
}

extension String {
    var md5: String {
        var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        if let data = data(using: .utf8) {
            data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
                CC_MD5(bytes, CC_LONG(data.count), &digest)
            }
        }
        
        var digestHex = ""
        for index in 0..<Int(CC_MD5_DIGEST_LENGTH) {
            digestHex += String(format: "%02x", digest[index])
        }
        
        return digestHex
    }
}

 1. didReceive: 方法中有一个等待发送的通知请求,我们通过修改这个请求中的 content 内容,然后在限制的时间内将修改后的内容调用通过 contentHandler 返还给系统,就可以显示这个修改过的通知了。

2. 在一定时间内没有调用 contentHandler 的话,系统会调用这个方法,来告诉你大限已到。你可以选择什么都不做,这样的话系统将当作什么都没发生,简单地显示原来的通知。可能你其实已经设置好了绝大部分内容,只是有很少一部分没有完成,这时你也可以像例子中这样调用contentHandler 来显示一个变更“中途”的通知。

Service Extension 现在只对远程推送的通知起效,你可以在推送 payload 中增加一个 mutable-content 值为 1 的项来启用内容修改:

 

用途:

使用在本机截取推送并替换内容的方式,可以完成端到端 (end-to-end) 的推送加密。你在服务器推送 payload 中加入加密过的文本,在客户端接到通知后使用预先定义或者获取过的密钥进行解密,然后立即显示。这样一来,即使推送信道被第三方截取,其中所传递的内容也还是安全的。使用这种方式来发送密码或者敏感信息,对于一些金融业务应用和聊天应用来说,应该是必备的特性。

推送通知中带了push payload,及时去年苹果已经把payload的size提升到了4k bites,但是这么小的容量也无法使用户能发送一张高清的图片,甚至把这张图的缩略图包含在推送通知里面,也不一定放的下去。在iOS X中,我们可以使用新特性来解决这个问题。我们可以通过新的service extensions来解决这个问题。

 

Content Extension:

可以用来自定义通知的详细页面的视图。新建一个 Notification Content Extension,Xcode 为我们准备的模板中包含了一个实现了UNNotificationContentExtension 的 UIViewController 子类。

这个 extension 中有一个必须实现的方法 didReceive(_:),在系统需要显示自定义样式的通知详情视图时,这个方法将被调用,你需要在其中配置你的 UI。而 UI 本身可以通过这个 extension 中的 MainInterface.storyboard 来进行定义。自定义 UI 的通知是和通知 category 绑定的,我们需要在 extension 的 Info.plist 里指定这个通知样式所对应的 category 标识符(值得提到的一点是,这里的extension是可以为一个数组的,里面可以为多个category,这样做的目的是多个category共用同一套UI。)我们自定义的内容和default conetnt的推送内容重复了,可以设置UNNotificationExtensionDefaultContentHidden为YES去掉



 

系统在接收到通知后会先查找有没有能够处理这类通知的 content extension,如果存在,那么就交给 extension 来进行处理。另外,在构建 UI 时,我们可以通过 Info.plist 控制通知详细视图的尺寸,以及是否显示原始的通知。关于 Content Extension 中的 Info.plist 的 key,可以在https://developer.apple.com/reference/usernotificationsui/unnotificationcontentextension中找到详细信息。

 虽然我们可以使用包括按钮在内的各种 UI,但是系统不允许我们对这些 UI 进行交互。点击通知视图 UI 本身会将我们导航到应用中,不过我们可以通过 action 的方式来对自定义 UI 进行更新。UNNotificationContentExtension 为我们提供了一个可选方法didReceive(_:completionHandler:),它会在用户选择了某个 action 时被调用,你有机会在这里更新通知的 UI。如果有 UI 更新,那么在方法的 completionHandler 中,开发者可以选择传递.doNotDismiss 来保持通知继续被显示。如果没有继续显示的必要,可以选择.dismissAndForwardAction 或者 .dismiss,前者将把通知的 action 继续传递给应用的UNUserNotificationCenterDelegate 中的userNotificationCenter(:didReceive:withCompletionHandler),而后者将直接解散这个通知。

 如果你的自定义 UI 包含视频等,你还可以实现 UNNotificationContentExtension 里的 media开头的一系列属性,它将为你提供一些视频播放的控件和相关方法。

 

Content Extension 界面:



 

上图,整个推送分4段。用户可以通过点击Header里面的icon来打开app,点击取消来取消显示推送。

Header的UI是系统提供的一套标准的UI。这套UI会提供给所有的推送通知。 

Header下面是自定义内容,这里就是显示的Notification content extension。在这里,就可以显示任何你想绘制的内容了。你可以展示任何额外的有用的信息给用户。

content extension下面就是default content。这里是系统的界面。这里的系统界面就是上面推送里面payload里面附带的内容。这也就是iOS 9 之前的推送的样子。

最下面一段就是notification action了。在这一段,用户可以触发一些操作。并且这些操作还会相应的反映到上面的自定义的推送界面content extension中。

 

 

 在xcode8中兼容iOS10和老系统:

1、

targets->capabilities->push notifications 必须打开,否则iOS会注册失败的

2、

虽然原来的 API 都被标为弃用了,但是如果你需要支持 iOS 10 之前的系统的话,你还是需要使用原来的 API。我们可以使用适配:

if #available(iOS 10.0, *) {

    // Use UserNotification

}

 

 感谢:

活久见的重构 - iOS 10 UserNotifications 框架解析 https://onevcat.com/2016/08/notification/

iOS8 notification http://justsee.iteye.com/blog/2144285

WWDC2016 Session笔记 - iOS 10  推送Notification新特性 http://www.jianshu.com/p/9b720efe3779

玩转 iOS 10 推送 —— UserNotifications Framework(上) http://www.jianshu.com/p/2f3202b5e758

玩转 iOS 10 推送 —— UserNotifications Framework(中) http://www.jianshu.com/p/5a4b88874f3a

iOS 推送全解析,你不可不知的所有 Tips!http://www.jianshu.com/p/e9c313df746f

iOS10 推送必看(基础篇) http://ios.jobbole.com/89277/

iOS10推送必看(高阶1)http://ios.jobbole.com/89283/

iOS推送——本地推送与远程推送详解(一图看懂)http://ios.jobbole.com/85200/

实现 iOS 前台时的推送弹窗效果 http://www.jianshu.com/p/67864e1c2085

 

 

 

 

 

 

  • 大小: 73.4 KB
  • 大小: 950.5 KB
  • 大小: 66.7 KB
  • 大小: 46.9 KB
分享到:
评论

相关推荐

    iOS10添加本地推送(Local Notification)实例

    前言 iOS 10 中废弃了 ...本文主要查看了 iOS 10 的相关文档,整理出了在 iOS 10 下的本地推送通知,由于都是代码,就不多做讲解,直接看代码及注释,有问题留言讨论哦。 新的推送注册机制 注册通知( Appdele

    整理官方极光推送

    官方极光推送提供了丰富的功能和API,支持Android和iOS平台,同时兼容多种开发环境。 在描述中提到的“只有一个build.gradle”,这通常指的是极光推送的库或者SDK被集成到一个项目中的方式。在Android开发中,`...

    消息推送.pdf

    例如,APNS是苹果公司提供的推送通知服务,用于iOS设备上的应用消息推送。SDK指的是软件开发工具包(Software Development Kit),为开发者提供在特定平台上构建应用的工具和接口。 此外,为了提升消息推送的效率和...

    iOS 17个常用代码整理

    在iOS开发中,掌握一些常用的代码片段可以极大地...17. **推送通知**:集成Apple Push Notification服务(APNs)接收远程通知。 这些是iOS开发中常见的代码和功能,熟练掌握它们能够帮助开发者高效地构建应用程序。

    十七种IOS常用代码整理

    除此之外,通知机制(Notification Center)、多任务处理(如后台音频播放)、地理位置服务(CoreLocation)以及推送通知(PushKit)等也可能在这些代码中有所体现。开发者可以学习如何设置监听器、获取用户位置信息...

    IOS开发资料集锦(很全,自己整理的)

    4. **iOS编程 (第2版)**:这是一部更深入的iOS开发书籍,可能包含iOS开发的高级主题,比如使用Swift 4或更新版本,Core Animation,Metal图形编程,推送通知服务,以及使用 SpriteKit 或 SceneKit 进行游戏开发。...

    html5服务器推送_动力节点Java学院整理

    当服务器有新的数据时,可以即时推送到客户端,客户端收到数据后自动触发指定的事件处理器。 SSE通讯协议包括两部分: 1. 服务器端:通过HTTP "text/event-stream" 类型的响应发送数据。响应可以包含多行,以分号...

    IOS面试宝典 最新

    六、推送通知 1. Apple Push Notification Service(APNs):理解推送机制,配置推送证书,处理推送接收。 2. Local Notifications:本地通知的设置与触发。 3. User Notification Framework:iOS 10及以后版本的...

    ios传智全套视频代码

    这可能涵盖了用户界面设计、数据存储(如Core Data或SQLite)、推送通知、地理位置服务等方面的知识。学习者可以通过这个项目了解如何构建一个完整的iOS应用,从需求分析到功能实现,再到测试和发布。 "视频"部分...

    UnityProject.rar

    此外,虽然本项目并未提及iOS平台,但实现本地推送通知在iOS上也有类似流程,主要区别在于需要使用苹果的Push Notification Service (APNS),并进行相应的证书配置。 总结,UnityProject.rar项目展示了如何在Unity...

    马上着手开发 iOS 应用程序 Start Developing iOS Apps Today

    - 例如:云同步、推送通知、地理位置服务等。 以上内容基于《马上着手开发iOS应用程序》文档整理而成,旨在帮助初学者快速入门iOS应用开发。随着实践经验的积累和技术的不断进步,开发者能够更好地理解和掌握这些...

    Xcode8以及iOS10适配等常见问题汇总(整理篇)

    4. 推送通知问题:在Xcode 8中,开发者需要在Targets -&gt; Capabilities中手动开启推送通知服务。此外,苹果对推送服务进行了重大调整,可能需要开发者进行额外的适配工作,比如更新推送证书和配置。 5. 字体大小变化...

    中文 iOSMac 开发博客列表.zip

    在描述中提到的"iOS开发",涵盖了许多子领域,如用户界面设计、网络编程、数据库操作、动画效果、推送通知、地图集成、游戏开发等。开发者需要学习如何使用苹果提供的APIs和框架来实现这些功能。 "中文 iOSMac 开发...

    ios视频教程.rar

    3. **iOS高级教程之项目实战**:这部分内容通常涉及真实世界的应用开发,可能包括地图集成(如MapKit)、推送通知、社交媒体整合、以及使用第三方库如Alamofire和AFNetworking进行网络请求。通过实际项目的演练,...

    IOS应用源码——书架.zip

    11. **Notification Service Extension**:对于阅读应用,可能还涉及到推送通知的扩展服务,用于在通知中预览书籍内容。 12. **权限管理**:如果应用需要访问用户的图书库或存储空间,源码中会包含请求用户授权的...

    iOS6新特征:PassKit编程指南

    最后,更新通行证和用户交互是PassKit框架的另一项重要功能,当通行证信息有变动时,开发者可以通过Apple推送通知服务(APNS)向用户推送更新信息,用户在接收到推送后将主动向服务器请求最新的通行证信息。...

    iOS部分重点

    9. **推送通知**: 设置和处理远程和本地推送通知,理解APNs(Apple Push Notification service)的工作流程。 10. **权限管理**: 如何请求和管理用户对照片、联系人、位置等敏感信息的访问权限。 11. **Core ...

    ios-常用功能集合.zip

    这个描述可能涵盖了广泛的iOS开发功能,比如网络请求(使用NSURLSession或第三方库如Alamofire)、数据持久化(CoreData、SQLite、UserDefaults)、推送通知(Remote Notifications)、图片处理(UIImage的裁剪、...

    ios mdm文档

    - **苹果推送通知证书**:可以通过 APN Portal 生成。 #### 二、MDM Check-In 协议 ##### 2.1 Check-In 请求结构 Check-In 请求包含了必要的认证信息以及设备的基本详情。 ##### 2.2 支持的 Check-In 命令 - **...

Global site tag (gtag.js) - Google Analytics