[iOS] 通知详解: UIUserNotification

通知相关系列文章
iOS10 之前通知使用介绍
[iOS] 通知详解: UIUserNotification
iOS10 相关API
[iOS] 通知详解:iOS 10 UserNotifications API
iOS10 本地/远程通知
[iOS] 通知详解: iOS 10 UserNotifications
iOS10 通知附加包
[iOS] 通知详解: iOS 10 UserNotifications – 附加包Media Attachments
iOS10 自定义UI
[iOS] 通知详解: iOS 10 UserNotifications – 自定义通知UI

本文主要是介绍iOS10之前的推送处理相关API及示例,很多文章都是将iOS10之前的API和iOS10新出的一起介绍,个人感觉比较混乱,而且不够清晰,所以才重新总结了一些文章并结合自己的使用经验,将 iOS10 之前的使用和iOS10之后的进行归纳。

通知权限请求

不管是本地通知,还是远程通知,都需要向用户申请通知的权限

UIUserNotificationSettings (iOS 8 ---- iOS 10)

UIUserNotificationSettings 主要由一个初始化方法来完成配置:

// types:通知的类型 见 UIUserNotificationType
// categories:自定义行为按钮,见 UIUserNotificationCategory,可传nil
public convenience init(types: UIUserNotificationType, categories: Set<UIUserNotificationCategory>?)

UIUserNotificationType 通知类型
public struct UIUserNotificationType : OptionSet {

    public init(rawValue: UInt)

    // 角标
    public static var badge: UIUserNotificationType { get } 
// 声音
    public static var sound: UIUserNotificationType { get } // the application may play a sound upon a notification being received
// 弹框
    public static var alert: UIUserNotificationType { get } // the application may display an alert upon a notification being received
}

一般使用(不带有动作按钮)
  • 注册通知设置
// categories 传nil,则通知没有额外的动作按钮
let setting = UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: nil)
        
UIApplication.shared.registerUserNotificationSettings(setting)

registerUserNotificationSettings 方法调用后,成功后会以代理的方式进行反馈

// registerUserNotificationSettings 回调代理
    func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
        // 注册 deviceToken
        application.registerForRemoteNotifications()
    }

这里我们需要调用registerForRemoteNotifications,来注册通知,获取deviceToken,也会以代理的方式进行反馈

// registerForRemoteNotifications 成功的回调,获取到token
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {

        let token = deviceToken.map { String(format: "%02hhx", $0) }.joined()
      
// Next do ...
// 一般是发送给自己的服务后台,或者第三方的推送服务器后台
    }

//    registerForRemoteNotifications 失败的回调
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print(error)
    }

到此,通知的权限申请及远程注册就完成了,接下来就是发送通知,本地或者远程。
此时,通知的弹框是没有其他动作按钮的,弹框下拉后,只有消息标题,内容等信息,没有额外的动作按钮

带有动作选择的通知

带有动作的通知,需要设置其 categories ,涉及到以下几个类的配置

UIUserNotificationCategory

携带动作的Category,一般是使用其可变实例 UIMutableUserNotificationCategory来添加多个行为:

// 唯一标识符
open var identifier: String?

    // Sets the UIUserNotificationActions in the order to be displayed for the specified context
    open func setActions(_ actions: [UIUserNotificationAction]?, for context: UIUserNotificationActionContext)

UIUserNotificationActions

动作实例,一般是使用其可变实例 UIMutableUserNotificationAction 来配置更多的行为

// 唯一标识符
    open var identifier: String?
    // 按钮 title
    open var title: String?
   // 用户点击该按钮时的行为,有两种
/*public enum UIUserNotificationActionBehavior : UInt { 
// 一般的点击事件
    case `default` // the default action behavior
// 点击按钮后会弹出输入框,可以输入一段文字快捷回复
    case textInput // system provided action behavior, allows text input from the user
}
*/
    // The behavior of this action when the user activates it.
    @available(iOS 9.0, *)
    open var behavior: UIUserNotificationActionBehavior

    
    // Parameters that can be used by some types of actions.
    @available(iOS 9.0, *)
    open var parameters: [AnyHashable : Any]

    // 按钮响应事件的模式,有两种
/*public enum UIUserNotificationActivationMode : UInt {
// 在该模式下,点击按钮后会打开app,可以在代理方法中做一些跳转操作
    case foreground // activates the application in the foreground
// 在该模式下,点击后不会打开app,可以后台做一些操作
    case background // activates the application in the background, unless it's already in the foreground
}
*/
    // How the application should be activated in response to the action.
    open var activationMode: UIUserNotificationActivationMode

    // 是否需要授权
    // Whether this action is secure and should require unlocking before being performed. If the activation mode is UIUserNotificationActivationModeForeground, then the action is considered secure and this property is ignored.
    open var isAuthenticationRequired: Bool

    // 当需要执行某些破坏性的操作时,需要醒目提醒,红色的按钮
    // Whether this action should be indicated as destructive when displayed.
    open var isDestructive: Bool

示例:

let ok = UIMutableUserNotificationAction()
        ok.identifier = "okidentifier"
        ok.activationMode = . foreground // 这里需要打开app,所以设置为foreground模式
        ok.title = "查看"
        ok.isDestructive = false
        ok.isAuthenticationRequired = false
        
        
        let cancel = UIMutableUserNotificationAction()
        cancel.identifier = "cancelidentifier"
        cancel.activationMode = .background // 这里不需要打开app,所以设置为background模式
        cancel.title = "关闭"
        cancel.isDestructive = true
        cancel.isAuthenticationRequired = false
        
        let category = UIMutableUserNotificationCategory()
        category.identifier = "categoryidentifier"
        category.setActions([ok, cancel], for: .default)
        
        
        let set = Set([category])
        
        let setting = UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: set)
        
        UIApplication.shared.registerUserNotificationSettings(setting)

效果,弹框下拉后,除了消息标题,内容等信息,还有添加的动作按钮

添加的按钮点击后,会调用下面的代理方法,本地通知和远程通知调用的方法不一样

// ios 8 --- ios 10
    func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, withResponseInfo responseInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
        
        print("handleActionWithIdentifier1 \(identifier)")
        
        completionHandler()
    }
    // ios 9 --- ios 10
    // 本地推送的UIMutableUserNotificationAction回调
    func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, completionHandler: @escaping () -> Void) {
        
        print("handleActionWithIdentifier2 \(identifier)")
        completionHandler()
    }

如果将创建 UIMutableUserNotificationAction 实例对象时的属性 behavior 设置为 .textInput,则点击按钮时,会弹出输入框:

输入的值,可以在代理方法的 responseInfo 获取:

// ios 8 --- ios 10
    func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, withResponseInfo responseInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
               
        let input = responseInfo[UIUserNotificationActionResponseTypedTextKey]
        print(input)

        completionHandler()
    }

本地通知

UILocalNotification(iOS 4.0 ---- iOS 10.0)

UILocalNotification 基本属性

UILocalNotification 常用的基本属性有以下几个:

// 本地通知指定处罚时间
    open var fireDate: Date?

// 通知弹框的内容
    open var alertBody: String?
// 通知弹框的标题
@available(iOS 8.2, *)
    open var alertTitle: String?
// 通知来时的提示音文件名称(需要加扩展名),UILocalNotificationDefaultSoundName为系统设置的默认提示音
    open var soundName: String? 
// 角标消息数量
    // badge
    open var applicationIconBadgeNumber: Int // 0 means no change. defaults to 0
// 通知代理方法调用后携带的参数内容
    // user info
    open var userInfo: [AnyHashable : Any]? 
// 动作按钮,此处的值要和设置的 UIUserNotificationCategory 实例的identifier属性值一致
    // category identifer of the local notification, as set on a UIUserNotificationCategory and passed to +[UIUserNotificationSettings settingsForTypes:categories:]
    @available(iOS 8.0, *)
    open var category: String?

//  触发时间所属的时区,默认是本机设定时区
    open var timeZone: TimeZone?
// 重复触发的频率,详见NSCalendar.Unit,例如 day:按天;month:按月;0 为不重复
    open var repeatInterval: NSCalendar.Unit 
// 指定日历,默认即可
    open var repeatCalendar: Calendar?

// 当进入/离开某个地理范围时触发本地通知;例如进入某地/某商场范围触发本地通知
// 需要有 "when-in-use" 的定位权限
    @available(iOS 8.0, *)
    @NSCopying open var region: CLRegion?
// 若为YES,每次超出/进入该范围都会触发;若为NO,则只触发一次;默认为YES
    @available(iOS 8.0, *)
    open var regionTriggersOnce: Bool

// 锁屏状态下,是否显示滑动的提示语,默认YES,iOS11好像没有这个提示语了
    open var hasAction: Bool
// 锁屏状态下,显示滑动的提示语,默认为“滑动解锁xxx”,如果设置此值为“打开xxx”,则是“滑动打开xxx”
    open var alertAction: String?

// 打开通知进入应用时的启动图片
    open var alertLaunchImage: String? 

添加/执行通知的方法
在通知设置完成后,调用下面的方法来执行/添加到执行队列

// 将本地通知添加到调度池,定时发送 fireDate 
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];  

// 立即发送
   [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];

本地通知的取消

// 取消某个本地通知,参数为通知的实例对象
open func cancelLocalNotification(_ notification: UILocalNotification)

// 取消所有待你执行的本地通知
    open func cancelAllLocalNotifications()
本地通知的处理

收到本地通知时,分两种情况来处理获取到的通知:

  • APP在前台运行,或者单机Home键切换到后台运行(未双击Home键完全退出)
    会调用下面的代理方法
    当前APP在前台运行时,会立即调用该方法;
    当APP从后台被唤起时,点击推送消息打开时调用
func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
        // 获取当前APP的状态
        let state = application.applicationState
        
        var msg = ""
        // 处在活跃状态,前台运行
        if state == .active {
            // qiantai
            
            msg += "qian tai"
        } else if state == .inactive {
// 处于不活跃状态,后台或后台被系统挂起
            // houtai
            msg += "hou tai"
        }
        // 注册通知时添加的 userInfo 信息
        msg += "\n\(notification.userInfo)"
        
        self.alert(msg)
        application.cancelLocalNotification(notification)
    }
  • APP 被杀死(双击Home键,从后台关闭)
    此时,应该从 didFinishLaunchingWithOptions 方法获取通知内容
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        // 应用被杀死后,如果来了通知,会在此处获取推送内容
       if let localNoti = launchOptions?[UIApplication.LaunchOptionsKey.localNotification] as? UILocalNotification {
            // NEXT TO DO ...
            print(localNoti.userInfo)
        }
        
        
        return true
    }

需要注意的是,此时虽然获取到了通知内容,但是,跟视图控制器可能还没有加载,业务的处理逻辑应该放到跟视图加载完成后再去处理。

如果某个推送已读,需要重置角标数量,可参考下面的方法

// 获取角标数量
        var badegNumber = UIApplication.shared.applicationIconBadgeNumber
        // 修改角标数量
        badegNumber -= 1
        // 重新赋值角标数量
        UIApplication.shared.applicationIconBadgeNumber = badegNumber >= 0 ? badegNumber: 0

远程通知 APNs(Apple Push Notification Services)

远程推送获取权限、自定义快捷操作以及获取 deviceToken 和本地通知相同,不同的只是收到通知后,回调的代理方法不一样。

  • 远程通知必须使用真机
  • 远程通知必须使用真机
  • 远程通知必须使用真机

测试推送服务

在开始之前,先介绍一个发送远程通知的软件:NWPusher,可以在GitHub下载后自己编译运行,也可以直接下载其二进制文件使用NWPusher app,打开后如下图:

NWPusher

如果不是真机,是获取不到deviceToken的
payload的内容模版为:

{"aps":{"alert":"Testing.. (0)","badge":1,"sound":"default"}}

key值aps是苹果定义的,推送的消息Json格式一定要是这样,除了此key,还有以下key值是苹果定义的:

  • alert :弹框内容,可以是 Dictionary
  • badge:角标的数量
  • sound:收到通知时播放的声音文件名,需要提前添加到Bundle中
  • content-availabel:如果其值为1,则是静默通知,且不能设置alert,sound,badge,否则无效
  • category:一组额外的快捷按钮标识符,即UIMutableUserNotificationCategory的identifier属性

注意: payload的长度是有限制的,
iOS 8以下是 256字节,
iOS 8 — iOS 9是2k,
iOS 9+ 是4k,
超过限制的消息,苹果后台是拒绝推送的。

如果需要添加自己的内容,直接在里面的字典添加自定义的key即可,例如:

{"aps":{"alert":"Testing.. (0)","badge":1,"sound":"default","extinfo": {}}}

然后,点击右下角的 push按钮即可发送一个远程通知。

PS:如果软件长时间不操作,点击push的报错,可点击右上角的Reconnect 重新连接服务即可!!!

一般远程通知

一般的远程通知注册和本地通知的注册一样,远程通知不需要本地创建通知对象,而是发送 payload 由系统来创建对应的对象,payload 就相当于给通知赋值:

let setting = UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: nil)
    
    UIApplication.shared.registerUserNotificationSettings(setting)

测试发送一个 Payload

{"aps":{"alert":"Remote Notification Test Info","badge":1,"sound":"default","extinfo": {"info": "Test remote info"}}}

使用上面介绍的软件发送:
Remote Notification

带快捷交互的通知

如果想要带有快捷交互的通知,需要在Payload中添加 category 字段,且其值要与注册通知时创建的 UIMutableUserNotificationCategory 实例的属性identifier值一致:
例如,注册的通知:

    func registerAPN() {
        
        let ok = UIMutableUserNotificationAction()
        ok.identifier = "okidentifier"
        ok.activationMode = . foreground // 这里需要打开app,所以设置为foreground模式
        ok.title = "查看"
        ok.isDestructive = false
        ok.isAuthenticationRequired = false
        ok.behavior = .default
        
        
        let cancel = UIMutableUserNotificationAction()
        cancel.identifier = "cancelidentifier"
        cancel.activationMode = .background // 这里不需要打开app,所以设置为background模式
        cancel.title = "关闭"
        cancel.isDestructive = true
        cancel.isAuthenticationRequired = false
        
        let category = UIMutableUserNotificationCategory()
        category.identifier = "categoryidentifier"
        category.setActions([ok, cancel], for: .default)
        
        
        let set = Set([category])
        
        let setting = UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: set)
        
        UIApplication.shared.registerUserNotificationSettings(setting) 
    }

将上面的Payload修改为:

{"aps":{"alert":"Remote Notification Test Info","badge":1,"sound":"default","category": "categoryidentifier","extinfo": {"info": "Test remote info"}}}

再次发送通知:

这时点击对应的快捷按钮,会调用系统代理方法:

// 远程推送的UIMutableUserNotificationAction回调
    func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
        
        if identifier == "okidentifier" {
            print("ok")
        } else {
            print("cancel")
        }
    }

其中的 userInfo 即是我们发送的Payload中的内容,在这里处理相应的业务逻辑即可;

如果,快捷操作的类型是输入框(iOS 9 — iOS 10),只需要将UIMutableUserNotificationAction示例对象的属性behavior修改为**.textInput** 即可,这时输入完成会调用方法:

func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable : Any], withResponseInfo responseInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
        
        let input = responseInfo[UIUserNotificationActionResponseTypedTextKey]
        print(input)
    }

远程通知的处理

同本地通知一样,远程通知的处理也分两种情况:

  • app 处于前台或后台(未双击Home键,将app杀死)
  • app 被杀死,双击Home键,将app从后台杀死

以上两种情况,如果来了远程通知,都会调用下面的代理方法:

// iOS 7 +
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        
        let state = UIApplication.shared.applicationState
        
        var msg = ""
        
        if state == .active {
            // qiantai
            
            msg += "active"
        } else {
            // houtai
            msg += "inactive"
        }
        
        print("Receive remote notification at \(msg)")
        print(userInfo)
        
        completionHandler(.noData)
    }

    // iOS 3 -- iOS 10
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
      
        print("didReceiveRemoteNotification old")
    }

iOS 7以后主要是使用第一个方法,只有在iOS 7以下的版本才会走第二个代理方法,现在几乎是使用不到了。

如果app正在前台运行,此时来了远程消息,会直接调用该代理方法,不会有弹框,通过 userInfo 可以获取推送的Playload信息;
如果app在后台运行或被系统挂起或被杀死,如果来了远程消息,就有弹框提醒,此时不会调用该方法,当点击消息打开app的时候,才会调用。

当app被杀死后,也可能会走didFinishLaunchingWithOptions方法:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        self.registerAPN()
        
        // 应用被杀死后,如果来了通知,会在此处获取推送内容
        if let pushNiti = launchOptions?[UIApplication.LaunchOptionsKey.remoteNotification] as? [String: Any] {
            
            print(pushNiti)
        }

        return true
    }

也可以添加如上的处理方法,现在一般都会走didReceiveRemoteNotification代理方法。

静默push iOS 7 +

静默push是没有弹框,没有声音,没有任何提醒的push,但是他能唤起你的app维持3分钟的运行,前提是你的app被切入后台运行或被系统挂起,而不是双击Home键杀死app。完全退出后,也是接收不到静默push的。他是iOS 7之后才出现的一种推送方式。
静默push的前提是:
payload中,没有alert及sound字段,而且添加content-available字段, 并设置其值为1:

{"aps":{"content-available":"1","extinfo": {"info": "Test remote info"}}}

同样,我们可以在接收一般通知的代理方法中来处理静默push的信息:

// iOS 7 +
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        
        let state = UIApplication.shared.applicationState
        
        var msg = ""
        
        if state == .active {
            // qiantai
            
            msg += "active"
        } else {
            // houtai
            msg += "inactive"
        }
        
        print("Receive remote notification at \(msg)")
        print(userInfo)
        completionHandler(.noData)
    }

一般我们需要在不打扰用户的情况下更新一些数据时,可以选择使用静默push,来后台下载更新相关的数据。

这里completionHandler的参数是UIBackgroundFetchResult类型,他有三个值:

@available(iOS 7.0, *)
public enum UIBackgroundFetchResult : UInt {

    // 新数据下载成功
    case newData
// 没有新数据需要下载
    case noData
// 新数据下载失败
    case failed
}

通知中心快捷回复

同本地通知一样,在通知中心快捷回复,只需要在创建UIMutableUserNotificationAction 实例对象时的属性 behavior 设置为 .textInput,则点击按钮时,会弹出输入框,同样是在下面的代理方法中可以获取到输入的内容:

func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable : Any], withResponseInfo responseInfo: [AnyHashable : Any], completionHandler: @escaping () -> Void) {
        
        let input = responseInfo[UIUserNotificationActionResponseTypedTextKey]
        print(input)
        
        completionHandler()
    }

到此,iOS10以下的通知处理基本就结束了,但是在iOS10中,这些处理方式都废弃了,使用新的API来处理通知:UserNotifications

后台任务示例代码

最后给一个后台任务的示例代码

func backgroundTask() {
        
        let app = UIApplication.shared
        
        var taskid = UIBackgroundTaskIdentifier.invalid
        
        taskid = app.beginBackgroundTask {
            
            app.endBackgroundTask(taskid)
            taskid = .invalid
        }
        
        let queue = DispatchQueue(label: "backgroundTaskQueue")
        queue.async {
            while true {
                let time = app.backgroundTimeRemaining
                if time < 5 {
                    break
                }
                
                Thread.sleep(forTimeInterval: 1)
            }
        }
        
        app.endBackgroundTask(taskid)
        taskid = .invalid
    }

参考文章

iOS下的UILocalNotification的使用

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值