[iOS] 通知详解: iOS 10 UserNotifications -- 自定义通知UI

通知相关系列文章
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

新建 Notification content extension

通知UI的自定义使用到了Notification content extension,同创建Notification Service Extension一样,我们需要创建一个新的 Target ,只不过这次选择Notification content extension

下一步,为这个Target起一个名字,完成即可!

可以看到多了下面几个文件:

这里的NotificationViewController就是我们编写自定义UI逻辑的控制器,他和一般的控制器一样,MainInterface.storyboard是与其绑定的,可以在此往storyboard添加控件。Info.plist为其相关的配置文件,有些操作需要在这里配置一些设置后,才能看到预期的效果,下面关于此部分的所有配置,都是在这里进行的。

NotificationViewController中,实现了UNNotificationContentExtension协议,他有两个协议方法

// 必须实现,用来处理自定义UI的内容
public func didReceive(_ notification: UNNotification)
// 选择实现,用来处理action的事件
optional public func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void)

第一个是必须要实现的,在NotificationViewController默认已经实现了,主要是处理当通知来的时候,布局自定义的UI内容以及相关的处理逻辑的地方;
第二个方法,当前发送的通知带有快捷操作action的时候(UNNotificationAction),来处理相关的点击事件。

因为我们自定义的任何View都是无法交互的,只能借助添加的action来处理相关的事件。

绑定 Category

Notification content extension添加完成后,在通知界面是看不到我们自定义的UI的,还需要绑定相关的 Category,即在创建通知的时候,我们添加的UNNotificationCategory,如果没有需要交互的action,可以传个空数组:

let category = UNNotificationCategory(identifier: "categoryidentifier", actions: [], intentIdentifiers: [], options: UNNotificationCategoryOptions.customDismissAction)
        UNUserNotificationCenter.current().setNotificationCategories(Set.init([category]))

然后在该扩展下的Info.plist中添加该Categoryidentifier,对应着UNNotificationExtensionCategory字段:

注意:这里的UNNotificationExtensionCategory可以修改为数组类型,如果我们有多个Category公用一套UI,可以将此值修改为Array类型,然后在数组里添加多个 Category 的identifier。

再去发送通知,注意此时的Payload中要添加category字段:

{
"aps":
    {
        "alert":
        {
            "title":"iOS10远程推送标题",
            "subtitle" : "iOS10 远程推送副标题",
            "body":"这是在iOS10以上版本的推送内容,并且携带来一个图片附件"
        },

"category":"categoryidentifier",
        "badge":1,
        "mutable-content":1,
        "sound":"default",
"image":"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3078873712,1340878922&fm=26&gp=0.jpg"
        
    }
}

弹框和锁屏页显示的内容和之前一样,打开通知或者下拉弹框,就会看到我们自定义的页面了:

比较丑的那部分就是我们自定义的UI了,可以看到真的很丑,大小还不合适,而且和系统默认的也重复的。

如果我们想要隐藏系统默认的内容页面,也就是下面的那部分,头是隐藏不了的;只需要在Info.plist里添加字段UNNotificationExtensionDefaultContentHidden,bool类型并设置其值为YES;

关于页面太大的问题,有的说通过修改其宽高比UNNotificationExtensionInitialContentSizeRatio的值,如果你的UI是固定的,可以通过适配大部分屏幕后,通过修改此值来得到合适的宽高比视图,但其值也是需要各种尝试的。
另外也可以使用autolayout,如果是在storyboard里添加的实图,顺便添加相应的约束即可;然后重新发送消息,大概就是这个样子:

这样,通知页面会先显示一个大的页面,然后再resize到约束后的页面大小,这样就会一个缩放的动画,这是因为在通知即将展示的时候,系统还没有调用我们的约束代码,也就是约束还没有起效,所以会有个resize的动画过渡。
为解决这个问题,只能在自定义UI的时候配合UNNotificationExtensionInitialContentSizeRatio设置合适页面大小,即采用固定的样式来展示通知内容。

显示附加包(attachment)的内容

如果我们的通知是携带附加包的,例如一张图片,添加自定义的UI后,打开通知或者下拉弹框会发现,大图不显示了,我们可以把相关的内容显示到自定义的UI上,还是以图片为例,在didReceive方法里添加以下获取附加包数据的代码:

if let att = notification.request.content.attachments.first {

            if att.url.startAccessingSecurityScopedResource() {
                self.coverImage.image  = UIImage(contentsOfFile: att.url.path)
                att.url.stopAccessingSecurityScopedResource()
            }
        }

这里需要说一下startAccessingSecurityScopedResourcestopAccessingSecurityScopedResource方法:
因为attachment是由系统单独管理的,所以这里我们在使用attachment之前,需要告诉iOS系统,我们需要使用它,并且在使用完毕之后告诉系统我们使用完毕了。对应上述代码就是startAccessingSecurityScopedResource()和stopAccessingSecurityScopedResource()的操作。当我们获取到了attachment的使用权之后,我们就可以使用那个文件获取我们想要的信息了。

再去发送上面的Payload,打开后就是这样了:

意思是那么个意思,但是加载的图片好像不太完整,上面我们是从attachment里面获取的,目前不清楚出现这个情况的原因,可能原数据被压缩了导致数据不全。所以,我们可以从发送的Payload中来获取数据:

if let aps = notification.request.content.userInfo["aps"] as? [String: Any] {

            if let imagePath = aps["image"] as? String {

                if let url = URL(string: imagePath) {

                    if let data = try? Data.init(contentsOf: url) {

                        self.coverImage.image = UIImage(data: data)
                    }
                }
            }
        }

这样就能正常显示了:

处理action事件

如果我们添加的category是带有action的,并且action的点击事件要响应到我们自定义的UI里面,例如点击的时候更换一个图片, 就需要UNNotificationContentExtension协议的另一个协议方法了:

// response:可以拿到点击的action,和通知的内容
// completion:处理完成后需要告诉系统,接下来该如何处理该通知
optional public func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void)

UNNotificationContentExtensionResponseOption 是一个枚举,他有三个值:

@available(iOS 10.0, *)
public enum UNNotificationContentExtensionResponseOption : UInt {

    // 通知页面不会消失,例如更新UI,显示出来
    case doNotDismiss
// 关闭当前通知页面
    case dismiss
// 将此action事件传递给app,在通知中心的代理方法里继续处理该事件
    case dismissAndForwardAction
}

需要注意的是,如果实现了此方法,就需要对所有添加的action进行处理,而不能只处理某个action

例如我们这样处理点击事件:

func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
// 改变标题
        self.label?.text = self.label?.text ?? "" + "点击了 "
        
        if response.actionIdentifier == "okidentifier" {
            // 点击了查看按钮,这里改变了标题的颜色

            self.label?.textColor = UIColor.red
            
            completion(.doNotDismiss)
        } else if response.actionIdentifier == "cancelidentifier" {
            // 点击了关闭,直接关闭通知
            completion(.dismiss)
        } else {
            // 如果还有其他的按钮,交给app处理
            completion(.dismissAndForwardAction)
        }
    }

然后,在创建通知的时候,添加相应的action:

let okAction = UNNotificationAction(identifier: "okidentifier", title: "查看", options: UNNotificationActionOptions.foreground)
   
        let cancel = UNNotificationAction(identifier: "cancelidentifier", title: "关闭", options: UNNotificationActionOptions.destructive)
        
        let category = UNNotificationCategory(identifier: "categoryidentifier", actions: [okAction, cancel], intentIdentifiers: [], options: UNNotificationCategoryOptions.customDismissAction)
        UNUserNotificationCenter.current().setNotificationCategories(Set.init([category]))

再次发生Payload,点击通知的查看action,会发现标题和标题的颜色都修改了。

处理快捷回复(输入文字)

前面知道,我们可以在通知中心进行快捷回复,只需要创建UNTextInputNotificationAction的action,添加到对应的category即可:

let okAction = UNTextInputNotificationAction(identifier: "okidentifier", title: "回复", options: .foreground, textInputButtonTitle: "快捷回复", textInputPlaceholder: "请输入。。。")
        
        
        let cancel = UNNotificationAction(identifier: "cancelidentifier", title: "关闭", options: UNNotificationActionOptions.destructive)
        
        let category = UNNotificationCategory(identifier: "categoryidentifier", actions: [okAction, cancel], intentIdentifiers: [], options: UNNotificationCategoryOptions.customDismissAction)
        UNUserNotificationCenter.current().setNotificationCategories(Set.init([category]))

这里,我们需要这样来处理接收到的反馈:

func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
        self.label?.text = "点击了 "
        
        if response.actionIdentifier == "okidentifier" {
            

            // 这里处理输入框的事件
            if let txRes = response as? UNTextInputNotificationResponse {
                // 如果是输入框的反馈,获取输入内容,显示出来
                let text = txRes.userText
                self.label?.text = text
            }
            
            // 点击了查看按钮,这里改变了标题的颜色
            self.label?.textColor = UIColor.red
            
            completion(.doNotDismiss)
        } else if response.actionIdentifier == "cancelidentifier" {
            // 点击了关闭,直接关闭通知
            completion(.dismiss)
        } else {
            // 如果还有其他的按钮,交给app处理
            completion(.dismissAndForwardAction)
        }
    }

然后在通知中心点击回复按钮的时候会弹出输入框,输入结束后,通知中心即显示了输入的内容:

到此,断断续续,总算是把通知相关的内容整体过了一遍,虽然感觉上还是有些逻辑混乱,基本上能够体现通知的一些使用方法。

参考文章
iOS10-UserNotifications
WWDC2016 Session笔记 - iOS 10 推送Notification新特性

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值