iOS10 之 iMessage Apps

還記得去年的 WWDC 嗎?當人們都在關注新出來的 Swift3、SiriKit、User Notifications 時,我卻關注起了它,沒錯,就是 iMessage Apps。那么它到底是什么東西呢?

它是什么

在 WWDC 的 Session 204 中如此描述它:

You will be able to write apps that run within the context of the Messages application.

直白點說就是我們可以通過編寫 iMessage App 來內嵌到系統的 Message 中,并進行各種交互。用戶通過系統的 Message 入口來使用自己開發的 iMessage App 豐富和擴展用戶在消息互動過程中的更多樂趣和功能。

目前 iMessage Apps 主要為用戶提供三種內容形式:

  • 第一種是交互式信息內容(Interactive message),發送者和接收者相互產生各種有趣交互效果。
  • 第二種是貼圖(Stickers)式的信息內容,通常貼圖以靜態圖片、動態圖片為主。
  • 第三種是照片、視頻、鏈接等樣式的信息內容。

好了,既然我們知道了 iMessage Apps 是什么,那么我們又該如何去開發呢?我們接著往下說:

如何開發

開發須知

  • 依賴的 Message.framework 框架(僅僅支持iOS10以及更高版本)。
  • 支持使用 Apple Pay支付、In-app purchase內購、Camera access相機訪問。
  • 開發的 iMessage Apps 也僅僅只在 iOS 10 以及更高版本中運行。
  • 目前在 WatchOS 平臺上可以接收和發送,Mac OS 平臺上則支持可以接收的。
  • Sticker文件大小最大不超過 500 KB。
  • 如果是動態Stickers的話,僅僅支持APNG、GIF 兩種格式。
  • Sticker支持這些格式:PNG、APNG、JPEG、GIF,當然蘋果最推薦還是APNG、PNG。
  • 當然了,到目前為止,iMessage App 的定位還是 Application 的Extension。如下圖所示:
14920048410015.jpg

開發 Sticker Pack Application

Sticker Pack Application 是一個由 Apple 提供的快速開發 iMessage App 模板,大家使用它來開發時僅僅只需要根據要求指定 Stickers.xcstickers 中的圖片即可。

  • 首先打開 Xcode,選擇 Sticker Pack Application ,創建之后,會看到當然項目中僅僅包含一個 Stickers.xcstickers 圖片集。如下圖所示:
8F460AB0-B0F0-496A-B878-F615407C3B1D.jpeg

該 Stickers.xcstickers 中包含了兩部分,分別是 iMessage App Icon 和 Stick Pack。想必大家應該都猜到了,iMessage App icon 指的是在系統的 Message 中展示的 iMessage App 圖標,而 Stick Pack 里面的圖片對應在大家開發的 iMessage App 中展示的各個貼圖。如下圖所示:

showStickerPackApplication.png

默認情況下,所展示的 Sticker Pack Application 中的圖片列表大小是 Regular型,除此之外,開發者還可以選擇 Small、Large 兩種尺寸。每一種尺寸類型都對應著不同的最大尺寸。這里蘋果也是建議大家盡可能選擇使用3x的圖片,以便達到最好的展示效果。

 Small     100 * 100pt @3x
 Medium    136 * 136pt @3x
 Large     206 * 206pt @3x

優點: 上手方便、快捷。
缺點: 無法滿足開發者的自定義需求。

開發簡單 Custom Sticker Application

相比較之前的 Sticker Pack Application 開發,Custom Sticker Application 要復雜一些、可定制化一些。它可以做到:

  • 自定義自己的 Application UI
  • 動態創建 Stickers
  • 應用中使用相機、使用內購

在開發過程中大家會涉及到如下類:

  • MessagesViewController
  • MSStickerBrowserViewController
  • MSStickerBrowserView
  • MSStickerBrowserViewDataSource
  • MSMessagesAppViewController

MessagesViewController 類個人覺得更像是一個容器類,它來承載和將要展示的視圖所在的控制器作為它的子控制器添加上去。同時,MessagesViewController 類當中的有些方法是用來處理子控制器上的事件回掉,而 MSStickerBrowserViewController 類則繼承了 UIViewController 且接受 MSStickerBrowserViewDataSource 協議(額……說的有點饒了),我們還是來看下面的圖:

showCustomStickerStructure.png

MSMessagesAppViewController 通常不會直接使用,使用是繼承于 MSMessagesAppViewController 的 MSMessagesViewController。
每一個貼圖就是一個 MSSticker對象,而 MSStickerView 對象中又包含了 MSSticker對象等等,這個,我們后面會有更詳細的介紹。

接著我們:

  • 打開 Xcode 選擇 iMessage Application,創建后會發現默認有了 MessagesViewController,相信大家剛開始看到 MessagesViewController 會有點懵掉,里面實現了很多方法,不過沒關系我們暫時只看 viewDidLoad() 就行。

  • 對于簡單的 Custom Sticker Application 開發,Apple 已經為了我們提供MSStickerBrowserViewController,我們只需要通過繼承該類并實現相關數據源協議方法。如下所示:

    //返回 Sticker 的個數
    override func numberOfStickers(in stickerBrowserView: MSStickerBrowserView) -> Int {
        return stickers.count
    }
    //返回每一個 Sticker 對象
    override func stickerBrowserView(_ stickerBrowserView: MSStickerBrowserView, stickerAt index: Int) -> MSSticker {
        return stickers[index]
    }

大家看到這里是不是有點像平時使用的 UITableView 或者 UICollectionView 的數據源協議呢?的確比較類似。這里,Stickers 數組包含了我要展示的所有 Sticker 數據,每一個元素都是 MSSticker 類型。如下是創建數組中元素的方法:

    //創建 MSStricker 對象的數組
    var stickers = [MSSticker]()
    
    //創建 MSSSticker 對象
    func createSticker(asset: String, localizedDescription: String)  {
    //獲取圖片路徑
    guard let stickerPath = Bundle.main.path(forResource: asset, ofType:"png") else {
            print("獲取圖片路徑失敗")
            return
        }
        let stickerURL = URL(fileURLWithPath: stickerPath)
        let sticker: MSSticker
        do {
            try sticker = MSSticker(contentsOfFileURL: stickerURL, localizedDescription: localizedDescription)
            //將創建的 MSStricker 對象添加到數組中
            stickers.append(sticker)
        } catch  {
            print(error)
            return
        }
    }

因為所有的圖片資源都放在 Bundle 中,所以通過獲取每一個圖片所在路徑,通過 URL 的 fileURLWithPath 方法轉化為 URL 對象,再調用 MSSticker 的contentsOfFileURL 方法得到 MSSticker 對象。

  • 到此,我們已經實現了 MSStickerBrowserViewController 中的數據源協議方法,回到 MessagesViewController 中,在 viewDidLoad() 中去創建GjStickerBrowerViewController 對象的實例,設置實例對象的視圖的 frame,最后將 GjStickerBrowerViewController 實例和相關 view 視圖都添加到當前的 MSMessagesViewController 及它的 View 視圖上。(當然了,添加的時候切記一定是先將 GjStickerBrowerViewController 控制器添加到
    MessagesViewController 上,然后再添加視圖到 MessagesViewController 的 view 上。)
    如下所示:
    //創建實例
    gjBrowerViewController = GjStickerBrowerViewController(stickerSize: .regular);
    //設置它的frame
    gjBrowerViewController.view.frame = self.view.frame
    //添加控制器及其視圖
    self.addChildViewController(gjBrowerViewController)
    gjBrowerViewController.didMove(toParentViewController: self)
    self.view.addSubview(gjBrowerViewController.view)

接著再去調用 GjStickerBrowerViewController 實例的加載數據方法。

    //加載數據
    gjBrowerViewController.loadStickers()
    gjBrowerViewController.stickerBrowserView.reloadData()

至此運行就會看到效果啦。

show.png

優點: 可以根據開發者的需求進行簡單自定義
缺點: 還是無法做到深度需求自定義

除了上面的那種簡單自定義方式之外,可能大家還是會覺得自定義的程度不夠或者不滿足自己的需求,沒關系,我們接著往下說另一種:

開發深度 Custom Sticker Application

除了上面的那種簡單自定義的 Sticker Application 之外,可能大家還是會覺得自定義的程度不夠或者不滿足自己的需求,那么下面的這種絕對適合你。這是一種更深度自定義的 Sticker Application。

  • 首先依然通過 Xcode 選擇 iMessage Application 項,那么既然是要深度自定義,干脆我們直接創建一個繼承于 UICollectionView Controller 好了,(當然,這里你也可以選擇繼承其他任意的控制器都行)。這個類主要用來承載展示各個貼圖的 view 所在控制器。我們先定義一個枚舉類型,用于 CollectionView 的 Item 類型。如下所示:
    enum CollectionViewItem {
        case sticker(MSSticker)
        case addSticker
    }
14920923598280.jpg
Sticker(MSSticker): 指代上圖中每一個貼圖圖片。
AddSticker        : 指代上圖中第一個加號圖片。
  • 其次,我們可以當前該控制器的 viewdidLoad() 中調用同上的填充數組數據的方法。loadStickers(),當然了,既然使用到了 UICollectionView 那就一定要實現它的數據源代理方法。如下所示:
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
    {
        return items.count
    }
    
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
    {
        let item = items[indexPath.row]
        switch item {
        case .addSticker:
            return dequeueAddStickerCell(at: indexPath)
        case .sticker(let sticker):
            return dequeueStickerCell(for: sticker, at: indexPath)
        }
    }

在上面的代碼中,分別對應創建了加號 Cell 類型和其余圖片 Cell 類型,如下所示:

class AddStickerCell: UICollectionViewCell {
    static let reuseIdentifier: String = "AddStickerCellIdentify"
    lazy var addImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.contentView.backgroundColor = UIColor.blue
        addImageView.center = self.contentView.center
        addImageView.image = UIImage(named: "add.png")
        self.addSubview(addImageView)
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
    private  func dequeueAddStickerCell(at indexPath: IndexPath) -> UICollectionViewCell 
    {
        let cell = collectionView?.dequeueReusableCell(withReuseIdentifier: AddStickerCell.reuseIdentifier, for: indexPath) as! AddStickerCell
        return cell
    }
    class StickerCell: UICollectionViewCell {
    static let reuseIdentifier = "StickerCellIdenfity"
    lazy var stickerView = MSStickerView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.addSubview(stickerView)
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
    private  func dequeueStickerCell(for sticker: MSSticker, at indexPath: IndexPath) -> UICollectionViewCell 
    {
        let cell = collectionView?.dequeueReusableCell(withReuseIdentifier: StickerCell.reuseIdentifier, for: indexPath) as! StickerCell
        cell.stickerView.sticker = sticker
        return cell
    }
  • 這個時候這個 UICollectionView 所在的頁面就基本上完成,我們再來到 MessagesViewController 類中,在其 viewDidLoad() 中創建 GJStickerCollectionViewController 類的實例并添加到MessagesViewController 上以及相應的視圖 view。如下所示:
    private  func loadStickerCollectionViewController() {
    
    let layout = UICollectionViewFlowLayout()
    layout.itemSize = CGSize(width: 100, height: 100)
    gjStickerViewController = GJStickerCollectionViewController(collectionViewLayout: layout)
    gjStickerViewController.view.frame = self.view.frame
    
    self.addChildViewController(gjStickerViewController)
    gjStickerViewController.didMove(toParentViewController: self)
    self.view.addSubview(gjStickerCollecitonViewController.view)    

    gjStickerViewController.delegate = self
    
    }
  • 接下來,我們在 GJStickerCollectionViewController 類中創建 GJStickerCollectionViewControllerDelegate 協議,并為協議添加如下方法:
    protocol GJStickerCollectionViewControllerDelegate: class {
        func gjStickerCollectionViewControllerDidSelectAdd(_ controller: GJStickerCollectionViewController)
}

它主要用于處理點擊加號事件并在設置代理:

    weak var delegate: GJStickerCollectionViewControllerDelegate?

與此同時,在 MessagesViewController 類中接收并實現該協議:

    extension MessagesViewController: GJStickerCollectionViewControllerDelegate {
        func gjStickerCollectionViewControllerDidSelectAdd(_ controller: GJStickerCollectionViewController) {
            //用于切換貼圖的展示樣式
            requestPresentationStyle(.expanded)
    }
}

到這里,一個可以深度自定義的簡單 iMessage Application 就算是完成了。

優點: 可以滿足自己多方面的自定義需求。
缺點: 需要自己開發的比較多,開發起來相對復雜。

Interactive Message

  • 第一,我覺得關于 Interactive Mesaage,大家應該先知道 Presentation Styles,它指的是 iMessage Application 的展示頁面樣式,分別是如下兩種:
14919275404464.jpg
1、Compact : 指的是擁擠的可以上下滑動的展示效果,
2、Expanded: 指的是擴散的,占滿全屏顯示的效果。
  • 第二,我們把與他人進行會話稱為一個 MSConversation,把每次發送出去的消息稱為一個 MSMessage。
14919286077612.jpg

每一個 Message 關聯了兩部分:
1、MsSession:用于創建和更新一個Message。
2、MSMessageTemplateLayout:用于會話的界面UI布局。

那么我們具體該如何去創建一個消息對象呢?

   //創建Message的UI布局
   let layout = MSMessageTemplateLayout()
   //創建Message的消息體
   let message = MSMessage(session: MSSession())
   message.url = URL(fileURLWithPath: "XXX")
   message.accessibilityLabel = "描述"
   message.summaryText = "summaryText"
   //設置Message的UI布局
   message.layout = layout

可以通過調用 insert 方法在當前對話中插入一個消息:

    //獲取當前會話
    let converson = self.activeConversation
    converson?.insert(message, completionHandler: { error in
        print("error")
    })

當然了,我們還可以通過如下一些方法來監聽事件:

    override func didStartSending(_ message: MSMessage, conversation: MSConversation) {
        //用戶點擊發送按鈕會觸發方法        
    }

    override func didReceive(_ message: MSMessage, conversation: MSConversation) {
        //接收到另一個遠端設備發送來的信息會觸發方法
    }
    ....

iMessage Applicaton 的生命周期

iMessage Application 的生命周期是非常簡單的,主要分成兩部分:

1、Becoming active(開始激活)

14919298443959.jpg

當用戶點擊了 Message Application 的應用圖標之后,它就會開始啟動,然后分別調用didBecomeActive、viewWillAppear、viewDidAppear 方法。

2、Resigning active

14919298895436.jpg

當用戶關掉當前屏幕中的對話頁面,內部會啟動 Resigning active,然后調用viewWillDisappear、viewDidDisappear、willResignActive 方法進而進程終止。

總結

整體而言,今天梳理了下關于 Messsage.framework、iMessage Application 開發等方面的知識,當然了,在我看來我僅僅寫了冰山一角,還有很多東西值得去研究。希望這篇文章能給大家帶來一定的幫助。

參考來源:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容