iOS 10 之 Messages framework
iMessage App 是一种全新的应用扩展,载体是 iOS 系统的 Message 应用,通过 iMessage App,用户可以发送更加丰富的消息内容,享受更具交互性的会话体验。
从 iOS 10 开始,消息扩展(Messages Extension)可以独立于宿主App(Container App) 开发,并提供了全新的消息模式。用户可以通过 Message 发送文本、表情包(sticker)、多媒体文件,以及其他可交互消息。
Messages App Store
Messages App Store 独立于之前的 App Store,只存在于系统的 Message 中,这里只显示 iMessage-related 应用。它可以干什么呢?
- 显示iMessage App;
- 为未安装应用的用户提供安装途径(Inline App Attribution);
- 提供iap,Apple Pay和访问相机功能。
iMessage App 可以独立开发(iOS 10之后),也可以作为 Extension 添加在现有项目中。
- 在现有项目中添加:File->New->Target->Application Extension->Sticker Pack Extension/iMessage Extension
- 独立开发:File->New->Project->Application->Sticker Pack Application/iMessage Application
基本表情包 (Sticker Packs)
Sticker 是 iOS 10 iMessage 引入的一种新的交互方式,可以当做消息发送,也可以附加在已有消息上。创建表情包不需要任何代码。图片文件需满足以下条件:
- 图片格式:PNG, APNG, GIF, JPEG;
- 文件大小:小于 500 KB;
- 图片大小区间:[100 x 100,206 x 206]
只需提供最大像素图片(@3x,[300 x 300,618 x 618]),系统会在需要的时候自动完成缩放。
苹果建议的表情文件大小:
- Small: 100 x 100 pt @3x scale (300 x 300 pixel image)
- Medium: 136 x 136 pt @3x scale (378 x 378 pixel image)
- Large: 206 x 206 pt @3x scale (618 x 618 pixel image)
打开 Xcode,创建新工程 BasicStickerPack:File->New->Project->Application->Sticker Pack Application。
左边侧栏有个Stickers.xcstickers
,包含了iMessage APP的icon 和表情,添加一组图片。如果要更改表情包默认名称和大小,可在右侧Attributes inspector 中更改。
运行,表情包将在 Message 的 Message App Store 打开,点击任何一个表情即可添加到当前信息并发送:
自定义表情包
基本的表情应用程序提供的模板可能不完全满足需求,我们创建一个较复杂的表情包应用程序 CustomStickerPack,选择 iMessage Application 模版。
创建好的工程相比上面的 Sticker Pack Application 多了 MessagesExtension 文件夹。该文件夹包含四个部分:
- MessagesViewController.swift : iMessage app的程序入口;
- MainInterface.storyboard: 可视化操作;
- Assets.xcassets: 图片集合;
- Info.plist : 配置一些扩展信息;
MessagesViewController
是 MSMessagesAppViewController
的子类,它是用来展示消息扩展(Message Extension)的界面。自定义 Sticker 需要自定义MSStickerBrowserViewController
。MSStickerBrowserViewController
是用来显示 Sticker 的,它有两个协议方法需要实现:
@protocol MSStickerBrowserViewDataSource <NSObject>
- (NSInteger)numberOfStickersInStickerBrowserView:(MSStickerBrowserView *)stickerBrowserView;//返回Sticker数量
- (MSSticker *)stickerBrowserView:(MSStickerBrowserView *)stickerBrowserView stickerAtIndex:(NSInteger)index;//返回MSSticker对象
MSStickerBrowserViewController
用法跟UITableViewDataSource
很像,有一个stickerBrowserView
对象:
@property (nonatomic, strong, readonly) MSStickerBrowserView *stickerBrowserView;
刷新数据,调用:
[stickerBrowserView reloadData];
一张图说明一下上述几个对象的关系:
将 sticker 放入 MessagesExtension 文件夹,创建一个 MSSticker
数组存储表情包,并写一个加载表情包的方法:
var stickers = [MSSticker]()
func loadStickers() {
for i in 1...2 {
if let url = Bundle.main.url(forResource: "Sticker \(i)", withExtension: "png"){
do {
let sticker = try MSSticker(contentsOfFileURL: url, localizedDescription: "")
stickers.append(sticker)
} catch {
print(error)
}
}
}
}
实现createStickerBrowser
方法, 初始化MSStickerBrowserViewController
作为根视图, 并设置宽高约束:
func createStickerBrowser() {
let controller = MSStickerBrowserViewController(stickerSize: .large)
addChildViewController(controller)
view.addSubview(controller.view)
controller.stickerBrowserView.backgroundColor = UIColor.blue
controller.stickerBrowserView.dataSource = self
view.topAnchor.constraint(equalTo: controller.view.topAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: controller.view.bottomAnchor).isActive = true
view.leftAnchor.constraint(equalTo: controller.view.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: controller.view.rightAnchor).isActive = true
}
实现MSStickerBrowserViewDataSource
的代理方法:
func numberOfStickers(in stickerBrowserView: MSStickerBrowserView) -> Int {
return stickers.count
}
func stickerBrowserView(_ stickerBrowserView: MSStickerBrowserView, stickerAt index: Int) -> MSSticker {
return stickers[index]
}
viewDidLoad
中加载 sticker 、创建 MSStickerBrowserViewController:
override func viewDidLoad() {
super.viewDidLoad()
loadStickers()
createStickerBrowser()
}
运行,跟上一个表情包差不多,但背景换成了自定义的颜色。
自定义 APP
本例将利用 iMessage App 创建一个独特的可交互消息。
创建 iMessage Application MessageApp
,在MainInterface.storyboard
添加一个 stepper 和 button:
选择 stepper 打开 Attributes inspector, 改变最小最大值为0和10:
介绍几个关键对象:
MSConversation
指当前打开的会话,可以通过 MSConversation 插入消息。
在MSMessagesAppViewController
中可以通过成员变量activeConversatio
n获取当前的MSConversation
,随后可调用以下方法插入不同的消息:
// 添加交互型消息.
open func insert(_ message: MSMessage, completionHandler: (@escaping (Error?) -> Swift.Void)? = nil)
// 添加 sticker.
open func insert(_ sticker: MSSticker, completionHandler: (@escaping (Error?) -> Swift.Void)? = nil)
// 添加文本.
open func insertText(_ text: String, completionHandler: (@escaping (Error?) -> Swift.Void)? = nil)
// 添加多媒体(音视频).
open func insertAttachment(_ URL: URL, withAlternateFilename filename: String?, completionHandler: (@escaping (Error?) -> Swift.Void)? = nil)
MSMessage
代表单个消息体,包含两个部分:
MSSession
:用来描述消息如何发送;MSMessageLayout
:用来描述消息如何展示。
在 iMessage 中自定义 MSMessage 必须要设置两个属性:layout
和url
。 url 是链接某个 web 页面的一些内容, 这样 MacOS 用户可以看到 iMessage 的内容。
MSMessageLayout
是一个抽象类,目前系统只提供了一种展现方法MSMessageTemplateLayout
,里边有许多属性和空间用来自定义message;:
左上角的空间是展示icon, 所有的属性都是可选的,提供任何标题字符串将摆脱底部部分的布局。
添加createImageForMessage()
方法,将当前的stepper的数值显示到圆形的label中, 然后将label放在UIImage对象中。
func createImageForMessage() -> UIImage? {
let background = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
background.backgroundColor = UIColor.white
let label = UILabel(frame: CGRect(x: 75, y: 75, width: 150, height: 150))
label.font = UIFont.systemFont(ofSize: 56.0)
label.backgroundColor = UIColor.red
label.textColor = UIColor.white
label.text = "\(Int(stepper.value))"
label.textAlignment = .center
label.layer.cornerRadius = label.frame.size.width/2.0
label.clipsToBounds = true
background.addSubview(label)
background.frame.origin = CGPoint(x: view.frame.size.width, y: view.frame.size.height)
view.addSubview(background)
UIGraphicsBeginImageContextWithOptions(background.frame.size, false, UIScreen.main.scale)
background.drawHierarchy(in: background.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
background.removeFromSuperview()
return image
}
设置 button 点击方法didPress
,该方法中先创建message的布局, 并且设置 image 和 caption, 接着创建 MSMessage 对象并插入到会话中:
@IBAction func didPress(button sender: AnyObject) {
if let image = createImageForMessage(), let conversation = activeConversation {
let layout = MSMessageTemplateLayout()
layout.image = image
layout.caption = "Stepper Value"
let message = MSMessage()
message.layout = layout
message.url = URL(string: "emptyURL")
conversation.insert(message, completionHandler: { (error) in
print(error)
})
}
}
在这个例子中, 我们只是创建了简单的字符 url,如果用户点击该消息并且 url 是 http(s) 类型的,系统会通过浏览器打开相应的页面。
运行 App:
点击 button 发送消息:
stepper+2:
iMessage App LifeCycle
看下 iMessage App 的生命周期。
启动过程
// Message Extension启动。
- (void)didBecomeActiveWithConversation:(MSConversation *)conversation;
- (void)viewWillAppear:(BOOL)animated;
- (void)viewDidAppear:(BOOL)animated;
销毁过程
- (void)viewWillDisappear:(BOOL)animated;
- (void)viewDidDisappear:(BOOL)animated;
- (void)willResignActiveWithConversation:(MSConversation *)conversation;
// Message Extension被系统销毁。
唤起过程
-(void)willTransitionToPresentationStyle:(MSMessagesAppPresentationStyle)presentationStyle;
-(void)didTransitionToPresentationStyle:(MSMessagesAppPresentationStyle)presentationStyle;
上面列出的回调方法均出现在MSMessagesAppViewController
中,因此,iMessage App 的生命周期就是MSMessagesAppViewController
的生命周期。
官方文档 Messages Framework Reference
WWDC Session1
WWDC Session2
官方 demo