iOS10 本地推送你玩过了吗?

首先来看一下iOS10的推送的基本的实现,和之前的推送有啥差别。

权限申请
iOS8之前,远程推送和本地推送是区分对待的,用户只要同意远程推送的是否允许就行了。iOS8对远程推送和本地推送权限允许进行了统一,无论是远程推送还是本地推送,在用户看来效果都是一样的,都是打断用户的操作。因此iOS8都需要申请权限。iOS 10 里进一步消除了本地通知和推送通知的区别。向用户申请通知权限非常简单:

首先看一下本地推送的相关的比较
下面是iOS10以下版本的本地推送
1、首先是权限问题

// 申请允许通知的权限
 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        registerPush(application: application)
        return true
    }
    
    // 注册推送
    func registerPush(application:UIApplication) {
        
        let settings = UIUserNotificationSettings.init(types: [.alert, .sound, .badge], categories: nil)
        application.registerUserNotificationSettings(settings)
        
    }

2、注册本地推送
ViewController中的操作,点击按钮,压入后台,等待4秒就会收到本地的推送

// 测试按钮的点击事件
    func clickBtn1(sender:UIButton) {

        //发送本地推送
        let notification = UILocalNotification()
        
        var fireDate = Date()
        fireDate = fireDate.addingTimeInterval(TimeInterval.init(4.0))
        notification.fireDate = fireDate
        notification.alertBody = "body"
        notification.userInfo = ["name":"张三","age":"20"]
        notification.timeZone = NSTimeZone.default
        notification.soundName = UILocalNotificationDefaultSoundName;
       
        //发送通知
        UIApplication.shared.scheduleLocalNotification(notification)
        UIApplication.shared.applicationIconBadgeNumber += 1;
        
    }

3、接收到本地推送的后续处理
当然了,不处理的话,就是默认进入app中。

// 接收到本地推送
    func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
        
        print("notification.userInfo = \(notification)  notification.alertBody = \(notification.alertBody)  notification.userInfo = \(notification.userInfo)");
    }

收到本地推送之后,点击推送的条,进入app,获取推送的相关的内容

notification.userInfo = <UIConcreteLocalNotification: 0x15e8a3d0>{fire date = 2016年11月28日 星期一 中国标准时间16:42:24, time zone = Asia/Shanghai (GMT+8) offset 28800, repeat interval = 0, repeat count = UILocalNotificationInfiniteRepeatCount, next fire date = (null), user info = {
    age = 20;
    name = "\U5f20\U4e09";
}}  notification.alertBody = Optional("body")  notification.userInfo = Optional([AnyHashable("age"): 20, AnyHashable("name"): 张三])

当然了,以上代码。iOS10系统的机器也是能够收到得到的,上下兼容嘛。
我们看一下,之前的本地推送的实现是这样的。


Paste_Image.png

简单的说就是本地推送通过 App 本地定制,加入到系统的 Schedule 里,然后在指定的时间推送指定文字。


下面是iOS10版本的本地推送
1、首先是权限问题
和iOS10以下的推送不同,iOS10的推送,是基于UserNotifications 框架实现的,所以在实现之前先导入 UserNotifications框架

import UserNotifications
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        registerPush(application: application)
        return true
    }
    
    // 注册推送
    func registerPush(application:UIApplication) {
        
        // 首先通过系统版本进行判断,进行不同的注册
        let version:NSString = UIDevice.current.systemVersion as NSString;
        let versionFloat = version.floatValue
        if versionFloat < 10{
            
            let settings = UIUserNotificationSettings.init(types: [.alert, .sound, .badge], categories: nil)
            application.registerUserNotificationSettings(settings)
            
        }else{
            
            if #available(iOS 10.0, *) {
                UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {
                    granted, error in
                    if granted {
                        // 用户允许进行通知
                        
                        print("用户允许进行通知了")
                        
                    }else{
                        print("用户不允许进行通知了")
                    }
                }
                
                // 向 APNs 请求 token:
                UIApplication.shared.registerForRemoteNotifications()
                
            } else {
                // Fallback on earlier versions
            }
        }
    }

2、接下来是注册相关的本地推送的操作,当然了也得导入头文件

import UserNotifications
    // 测试按钮的点击事件
    func clickBtn2(sender:UIButton) {
        
        if #available(iOS 10.0, *) {
            // 1、创建推送的内容
            let content = UNMutableNotificationContent()
            content.title = "iOS 10 的推送标题"
            content.body = "iOS 10 的推送主体"
            content.subtitle = "iOS 10 的副标题"
            content.userInfo = ["name":"张三","age":"20"]
            
            // 2、创建发送触发
           
            /* 触发器分三种:
             UNTimeIntervalNotificationTrigger : 在一定时间后触发,如果设置重复的话,timeInterval不能小于60
             UNCalendarNotificationTrigger: 在某天某时触发,可重复
             UNLocationNotificationTrigger : 进入或离开某个地理区域时触发 */
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false)
            
            // 3. 发送请求标识符
            let requestIdentifier = "firstLocationPush"
            
            // 4、创建一个发送请求
            let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)
            
            // 5、将请求添加到发送中心
            UNUserNotificationCenter.current().add(request, withCompletionHandler: { (error) in
                if error == nil{
                    print("Time Interval Notification scheduled: \(requestIdentifier)")
                }
            })
        } else {
            // Fallback on earlier versions
        }
    }

UserNotifications 中对通知进行了统一。我们通过通知的内容 (<code>UNNotificationContent</code>),发送的时机(<code>UNNotificationTrigger</code>) 以及一个发送通知的 String类型的标识符,来生成一个 <code>UNNotificationRequest</code>类型的发送请求。最后,我们将这个请求添加到 <code>UNUserNotificationCenter.current()</code>中,就可以等待通知到达了。
那么本地推送的实现就变成了这样:
<a href="http://www.jianshu.com/p/2f3202b5e758">图片来自</a>


Paste_Image.png

Local Notifications 通过定义 <code>Content</code> 和 <code>Trigger</code> 向<code> UNUserNotificationCenter</code> 进行 <code>request</code> 这三部曲来实现。

PS:补充一下


 /* 触发器分三种:
UNTimeIntervalNotificationTrigger : 在一定时间后触发,如果设置重复的话,timeInterval不能小于60
             UNCalendarNotificationTrigger: 在某天某时触发,可重复
             UNLocationNotificationTrigger : 进入或离开某个地理区域时触发 */
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: true)

这么写崩溃了,原因是这个样子滴。设置时间大于60S就行了


Paste_Image.png

3、iOS10的本地推送在哪里接收
当然了,使用<code>UNNotificationContent</code>创建的本地通知,也是可以通过<code>Appdelegate</code>类中的

func application(_ application: UIApplication, didReceive notification: UILocalNotification)

方法的。
不过,最好是通过UNUserNotificationCenterDelegate的相关的代理方法进行读取,以及相关的后续操作。

Paste_Image.png

我们创建一个新的类<code>NotificationHandler</code>,来遵守代理,进行相关的操作

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    let notificationHandler = NotificationHandler()
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        
        // 注册推送
        registerPush(application: application)
        
        // 添加iOS10 推送的相关的代理
        if #available(iOS 10.0, *) {
            UNUserNotificationCenter.current().delegate = notificationHandler
        } else {
            // Fallback on earlier versions
        }
        
        return true
    }
}
import UIKit
import UserNotifications
class NotificationHandler: NSObject,UNUserNotificationCenterDelegate {

    @available(iOS 10.0, *)
    // 点击推送框的时候,就会走这个方法。不管是本地推送还是远程推送,相比之前更加方便了
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        
    }
    
    @available(iOS 10.0, *)
    // 展示之前进行的设置
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        
    }
}

首先熟悉下<code>func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void)</code>
获取一个本地推送的相关的信息

 @available(iOS 10.0, *)
    // 点击推送框的时候,就会走这个方法。不管是本地推送还是远程推送,相比之前更加方便了
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        
        let userInfo = response.notification.request.content.userInfo
        let title = response.notification.request.content.title
        let subTitle = response.notification.request.content.subtitle
        let categoryIdentifier = response.notification.request.content.categoryIdentifier
        let body = response.notification.request.content.body
        let content = response.notification.request.content
        let identifier = response.notification.request.identifier
        
        print("  userInfo = \(userInfo)\n  title = \(title)\n  subTitle = \(subTitle)\n  categoryIdentifier = \(categoryIdentifier)\n  body = \(body)\n  identifier = \(identifier)  \n  content = \(content)\n  ")
        
    }

打印结果

 userInfo = [AnyHashable("name"): 张三, AnyHashable("age"): 20]
  title = iOS 10 的推送标题
  subTitle = iOS 10 的副标题
  categoryIdentifier = firstLocationPush
  body = iOS 10 的推送主体
  identifier = firstLocationPush  
  content = <UNNotificationContent: 0x1742e3800; title: iOS 10 的推送标题, subtitle: iOS 10 的副标题, body: iOS 10 的推送主体, categoryIdentifier: firstLocationPush, launchImageName: , peopleIdentifiers: (
), threadIdentifier: , attachments: (
), badge: (null), sound: (null), hasDefaultAction: YES, shouldAddToNotificationsList: YES, shouldAlwaysAlertWhileAppIsForeground: NO, shouldLockDevice: NO, shouldPauseMedia: NO, isSnoozeable: NO, fromSnooze: NO, darwinNotificationName: (null), darwinSnoozedNotificationName: (null)

了解下<code>func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void)</code>方法

当收到推送,不管是远程的还是本地的,在展示推送之前都会先走这个方法,可以打断点测试。
 @available(iOS 10.0, *)
    // 展示之前进行的设置
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        
let identifier = notification.request.identifier
        if identifier == "firstLocationPush"{
            
            completionHandler([.alert, .sound])
            
        }else{
            // 如果不想显示某个通知,可以直接用空 options 调用 completionHandler:
            completionHandler([])
            
        }
    }

之前的推送,当app在前台是不能显示的,iOS10就可以,怎么实现?上面已经实现了。在上面的方法中可以通过不同的标识来进行不同的设置,有的推送收到了可以在前台显示,有的只能在后台显示。


iOS10的推送增加了相关的交互功能。如图所示:
收到推送之后下拉推送即可展示。


Paste_Image.png

Paste_Image.png

上代码:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        
        // 注册推送
        registerPush(application: application)
        
        registerNotificationCategory()
        
        // 添加iOS10 推送的相关的代理
        if #available(iOS 10.0, *) {
            UNUserNotificationCenter.current().delegate = notificationHandler
        } else {
            // Fallback on earlier versions
        }
        
        return true
    }
// 设置特殊标识的通知条的交互
    private func registerNotificationCategory() {
        if #available(iOS 10.0, *) {
            let saySomethingCategory: UNNotificationCategory = {
                // 1
                let inputAction = UNTextInputNotificationAction(
                    identifier: "input",
                    title: "Input",
                    options: [.foreground],
                    textInputButtonTitle: "Send",
                    textInputPlaceholder: "What do you want to say...")
                
                // 2
                let goodbyeAction = UNNotificationAction(
                    identifier: "goodbye",
                    title: "Goodbye",
                    options: [.foreground])
                
                let cancelAction = UNNotificationAction(
                    identifier: "cancel",
                    title: "Cancel",
                    options: [.destructive])
                
                // 3
                return UNNotificationCategory(identifier:"saySomethingCategory", actions: [inputAction, goodbyeAction, cancelAction], intentIdentifiers: [], options: [.customDismissAction])
            }()
            
            UNUserNotificationCenter.current().setNotificationCategories([saySomethingCategory])
            
        } else {
            // Fallback on earlier versions
        }
    }

以上代码可以理解为提前注册一个特殊标识的<code>UNNotificationCategory</code>
等收到<code>categoryIdentifier = "设置的categoryIdentifier"</code>的时候就会展示相关设置的交互界面。


Paste_Image.png

这样的通知在哪里接收,并且做相关的操作呢?一样,还是在<code>UNUserNotificationCenterDelegate</code>的代理中进行接收


Paste_Image.png

主要代码

 // 获取特殊的 categoryIdentifier 的操作信息
        if categoryIdentifier == "saySomethingCategory" {
            let text: String
            if let actionType = SaySomethingCategoryAction(rawValue: response.actionIdentifier) {
                
                switch actionType {
                case .input:
                    text = (response as! UNTextInputNotificationResponse).userText
                case .goodbye:
                    text = "Goodbye"
                case .none:
                    text = ""
                }
            }else{
                text = ""
            }
            
            print("text ===== \(text)")
        }

取消和更新
取消
先来看一下取消通知吧

取消已经显示过的通知

// 取消已经显示过的通知,指定相关的 Identifier
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [requestIdentifier])

取消所有已经展示过的所有的通知

UNUserNotificationCenter.current().removeAllDeliveredNotifications()

在之前的推送中上面的这两种功能还是挺常见的。

取消正在等待显示的通知,也就是提交了未显示的通知

UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [requestIdentifier])

取消所有正在等待显示的通知,也就是提交了未显示的通知

UNUserNotificationCenter.current().removeAllPendingNotificationRequests()

更新
当我们更新相关的通知,只要将还没有展示的通知,再次添加到<code>UNUserNotificationCenter.current()</code>中就行,记得<code>identifier</code>要保持一致奥,不然创建出来的是一个新的通知。

在通知条中展示多媒体的信息
这个可以说是iOS10通知的一大特色。
直接上代码,其实几句话就搞定了,当然了,苹果支持的文件类型和大小也是有相关限制的,具体请参考:
https://developer.apple.com/reference/usernotifications/unnotificationattachment

Paste_Image.png

主要代码

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

实现效果:
图片的

Paste_Image.png

Paste_Image.png

音频的,可以直接播放的


Paste_Image.png

视频的,也是可以直接播放的,但是好像是没有声音的


Paste_Image.png

Paste_Image.png

本地推送的简单的实现,就尝试这么多。
最后,献上参考Demo地址:https://github.com/RunOfTheSnail/PushDemo001

下一章<a href="http://www.jianshu.com/p/5d83250fb5a1">iOS10 远程推送你玩过了吗?</a>

参考资料:
http://www.cocoachina.com/ios/20160628/16833.html
https://onevcat.com/2016/08/notification/
http://www.cnblogs.com/lidongq/p/5968923.html
https://developer.apple.com/reference/usernotifications/unnotificationattachment
图画的不错
http://www.jianshu.com/p/2f3202b5e758
http://www.cocoachina.com/ios/20161021/17820.html

推荐阅读更多精彩内容