iOS推送小结
文章的Demo地址:iOS-Push。
Demo中的推送测试可以使用类似 Easy APNs Provider的工具,结合自己的证书进行测试。
1.普通推送基本设置
1.1 创建项目,开启远程推送功能
在Cababilities中打开Push Notification开关
WX20170815-184013.png
5. Notification Content Extension
Notification Content Extension
是一个定制化展示本地和远程通知的插件,开发者可以自定义其中展示的内容,常常会结合上面的Notification Service Extension插件和UNNotificationCategory
、 UNNotificationAction
使用做成带有交互的推送内容。
整体流程为:
- 注册
Notification Category
,其中包含Action. - 推送Mutable-Content的通知,在Service Extension中下载对应的多媒体消息,重新生成通知内容,并指定通知的
categoryIdentifier
。 - 用户3D-Touch推送会启动
Notification Content Extension
,在其中进行通知的定制化展示。 - 用户触发交互(即
UNNotificationAction
)后,在UNUserNotificationCenter
代理方法中进行处理。在Notification Content Extension
中也可以进行初步处理,并决定是否将Action转发到UNUserNotificationCenter
。
整体效果:
ExampleDemo推送内容:
{
"aps" : {
"alert" : {
"title" : "Message",
"body" : "Your message Here"
},
"badge" : 1,
"content-available" : 1,
"mutable-content" : 1,
"catId" : "action1" // 自定义字段,
}
}
</br>
5.1 创建Notification Content Extension
新建一个Target,选择Notification Content Extension
,其BundleId应该在原项目BundleId的命名空间下。
创建后会增加Target的文件:
ContentExtensionFiles
在.h
中可以看到其实这是一个UIViewController
子类,我们可以添加各种视图。
// NotificationViewController.h
#import <UIKit/UIKit.h>
@interface NotificationViewController : UIViewController
@end
// NotificationViewController.m
@interface NotificationViewController () <UNNotificationContentExtension>
在.m
中可以看到这个控制器遵守UNNotificationContentExtension
协议,协议中有如下方法和属性:
@protocol UNNotificationContentExtension <NSObject>
// This will be called to send the notification to be displayed by
// the extension. If the extension is being displayed and more related
// notifications arrive (eg. more messages for the same conversation)
// the same method will be called for each new notification.
- (void)didReceiveNotification:(UNNotification *)notification;
@optional
// If implemented, the method will be called when the user taps on one
// of the notification actions. The completion handler can be called
// after handling the action to dismiss the notification and forward the
// action to the app if necessary.
- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption option))completion;
// Implementing this method and returning a button type other that "None" will
// make the notification attempt to draw a play/pause button correctly styled
// for that type.
@property (nonatomic, readonly, assign) UNNotificationContentExtensionMediaPlayPauseButtonType mediaPlayPauseButtonType;
// Implementing this method and returning a non-empty frame will make
// the notification draw a button that allows the user to play and pause
// media content embedded in the notification.
@property (nonatomic, readonly, assign) CGRect mediaPlayPauseButtonFrame;
// The tint color to use for the button.
@property (nonatomic, readonly, copy) UIColor *mediaPlayPauseButtonTintColor;
// Called when the user taps the play or pause button.
- (void)mediaPlay;
- (void)mediaPause;
@end
@interface NSExtensionContext (UNNotificationContentExtension)
// Call these methods when the playback state changes in the content
// extension to update the state of the media control button.
- (void)mediaPlayingStarted __IOS_AVAILABLE(10_0) __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __OSX_UNAVAILABLE;
- (void)mediaPlayingPaused __IOS_AVAILABLE(10_0) __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __OSX_UNAVAILABLE;
@end
除了Require的方法之外,didReceiveNotificationResponse:completionHandler:
负责处理推送Action交互,而其他的用来控制视频的播放。下面的示例中会使用到。
最下方还有一个NSExtesnsionContext
类,暂时不清楚它怎么使用。
Content Extension的Info.plist中的内容:
Info
UNNotificationExtensionDefaultContentHidden
,插件默认会展示推送的内容(Title、subtitle、body,不展示Attachment
),通过这对键值来控制是否隐藏原始内容。
UNNotificationExtensionCategory
,值类型可以为String/Array,通知的类别,只有类别ID在此之中的通知才会进入Notification Content Extension
中被处理。
UNNotificationExtensionInitialContentSizeRatio
, 视图的宽高比。视图的最终大小(主要是高度),会受VC的preferredContentSize
、sb中的约束和视图高度、这个比例3者的影响。优先级从前到后下降。
5.2 编码
首先在申请通知权限成功后,设置通知的类别和Action
UNNotificationAction *action1 = [UNNotificationAction actionWithIdentifier:@"checkoutAction" title:@"查看" options:UNNotificationActionOptionAuthenticationRequired|UNNotificationActionOptionForeground];
UNTextInputNotificationAction *action2 = [UNTextInputNotificationAction actionWithIdentifier:@"replyAction" title:@"回复" options:0 textInputButtonTitle:@"发送" textInputPlaceholder:@"回复消息"];
// 此处categoryIdentifier应该是上面Info.plist中UNNotificationExtensionCategory包含的值
UNNotificationCategory *cat = [UNNotificationCategory categoryWithIdentifier:@"action1" actions:@[action1,action2] intentIdentifiers:@[] options:UNNotificationCategoryOptionNone];
[center setNotificationCategories:[NSSet setWithObjects:cat, nil]];
[center getNotificationCategoriesWithCompletionHandler:^(NSSet<UNNotificationCategory *> * _Nonnull categories) {
NSLog(@"get cat:%@",categories);
}];
在Notification Service Extension
中设置推送的categoryIdentifier
,如果应用采用了多种Category,一般应该这个标识符包含在推送内容中。
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// 此处省略推送内容的其他修改和附件的下载
// 下载完成后,使用fileUrl创建附件
UNNotificationAttachment *atm = [UNNotificationAttachment attachmentWithIdentifier:@"" URL:url options:options error:&error];
self.bestAttemptContent.attachments = @[atm];
// 设置categoryIdentifier
self.bestAttemptContent.categoryIdentifier = request.content.userInfo[@"aps"][@"catId"];
self.contentHandler(self.bestAttemptContent);
}
在Notification Content Extension
中定制视图,展示推送内容,此处以视频附件为例。
声明协议中与视频播放相关的属性,实现对应的方法。
@interface NotificationViewController () <UNNotificationContentExtension>
@property IBOutlet UILabel *label;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (nonatomic, strong) AVPlayerLayer *layer;
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, assign) UNNotificationContentExtensionMediaPlayPauseButtonType mediaPlayPauseButtonType;
@property (nonatomic, assign) CGRect mediaPlayPauseButtonFrame;
@property (nonatomic, copy) UIColor *mediaPlayPauseButtonTintColor;
@end
@implementation NotificationViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 设置ContentSize
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300);
}
- (void)didReceiveNotification:(UNNotification *)notification {
//
self.label.text = notification.request.content.body;
UNNotificationAttachment *atm = notification.request.content.attachments.firstObject;
if ([atm.URL startAccessingSecurityScopedResource]) {
self.player = [AVPlayer playerWithURL:atm.URL];
self.layer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.layer.frame = SomeRect;// frame自行计算,此处仅为示例
self.layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.view.layer addSublayer:self.layer];
[atm.URL stopAccessingSecurityScopedResource];
}
}
- (UNNotificationContentExtensionMediaPlayPauseButtonType)mediaPlayPauseButtonType {
return UNNotificationContentExtensionMediaPlayPauseButtonTypeOverlay;
}
- (CGRect)mediaPlayPauseButtonFrame {
CGPoint center = self.imageView.center;
return CGRectMake(center.x - 25, center.y - 25, 50, 50);
}
- (UIColor *)mediaPlayPauseButtonTintColor {
return [UIColor lightGrayColor];
}
- (void)mediaPlay {
[self.player play];
}
- (void)mediaPause {
[self.player pause];
}
@end
由于在视图初始化时,还不能知道推送内容的最终高度,因此最好以一个固定的高度呈现。上面在代码中使用preferredContentSize来设置。
代码中使用AVPlayer和AVPlayerLayer来展示视频附件,其中获取视频URL时,由于Attachment是由系统管理,在沙盒之外,我们访问URL内容时候需要先获取使用权限:
if ([atm.URL startAccessingSecurityScopedResource]) {
...
[atm.URL stopAccessingSecurityScopedResource];
}