本文授權轉載,作者:Cyandev(簡書)
從 iOS 8 開始,蘋果引入了全新的 App Extension,涉及到方方面面,例如今日面板、鍵盤、內容攔截器、分享動作等。但是官方對於 App Extension 的開發指南少之又少,入門起來會有很多坑。所以我准備寫一系列文章來幫助大家更好入門 App Extension 的開發,也能少走彎路。
何為 App Extension?
顧名思義,它是一種擴展,很類似於一些大型軟件(好吧,現在可能是個應用都可以有)的插件機制。App Extension 事實上並不是你應用的插件,而是系統的插件,其生命周期是由系統來管理的,所以如果你想做什麼壞事還是行不通的...但是 App Extension 分發的載體是應用,也就是說如果你只是單純想做一個今日面板插件,也需要有個主程序,你的主程序可以什麼都不做,也可以提供一些基本的設置和數據。
App Extension 和主程序的關系?
可以說沒有什麼關系,基本上就是兩個獨立的程序,你的主程序既不可以訪問 App Extension 的代碼,也不可以訪問其存儲空間,這完完全全就是兩個進程、兩個程序。這時你可能會問,我擦,都不能交互那有什麼卵用??別急,後面我會講到如何做一些交互。
App Extension 可以干什麼?不可以干什麼?
基本上什麼都能干,有人不是在今日面板裡把 Chrome 的恐龍小彩蛋硬塞進去了嗎?還有拿輸入法當浏覽器作分屏多任務的...只有你想不到,沒有你做不到......诶等等,有些還是做不到的。比如,內存有限制,App Extension 的可用內存遠不如常規應用,以至於如果你真想做游戲,還是掂量掂量你的資源占用問題能不能解決吧。而且還不能訪問 UIApplication,因為它的容器應用是系統,你拿系統的 UIApplication 想干嘛...(當然,你可以用遞歸查找 UIResponder 的方法拿到 UIApplication,但是我沒試過)再次,你不能執行長時間的操作,你的 App Extension 可能隨時被系統 Kill 掉,who knows?
還有更多不可用的 API 可以看這個蘋果官方文檔:Understand How an App Extension Works
開始創建一個 App Extension
首先看一下我們要做的東西,是一個簡單 Todo 應用,主程序長這個樣:
今日面板插件長這個樣:
界面都很簡單啦~
主程序實現其實很簡單,就是 Table View 的使用以及數據持久化,這裡就不著重講了。但注意,我們要留出一個接口給今日面板,假設這裡我們要在今日面板裡顯示前 4 條待辦事項,我們必須要單獨將這 4 條存在一個主程序和擴展都能訪問的地方。後面我會說怎麼做。
Tips:
蘋果的 HIG 明確指出,不要在今日面板裡使用可以滾動的 Scroll View,而是要完全展開,因此對於多條數據,我們要不就分頁,要不就只顯示前幾項。
下面,我們就為工程創建一個 Today Extension:
一路下一步,輸入一個子項目名,點 『Finish』就完成 Today Extension 的添加了。
這個子項目的初始目錄結構如下:
P.S. 那個 entitiements 文件是後來創建的,一開始不會有
然後我們在 Storyboard 裡把大致界面拖出來,如果畫布大小不合適可以在這調整一下,但是也就是調整了預覽效果,真實的大小不能在 IB 裡修改。
那我們怎麼修改視圖在今日面板裡的大小呢?答案是修改 View Controller 的 preferredContentSize 屬性,不理會 width,調整 height 到合適的大小即可,因為寬度總是和屏幕寬度相同的。
在這個例子中,我使用 44 * 項目數量 - 1 來作為視圖高度,因為一個標准 Table View Cell 的高度是 44,然後減掉最後一個條目分割線的高度就是我們理想的合適高度。
主程序向 App Extension 共享數據
我們在主程序裡創建了待辦事項,怎麼才能讓 App Extension 獲取到呢?由於兩者代碼和數據都不互通,所以我們 可以理所當然的想到用 App Group 來解決。首先在主程序中創建一個 App Group:
然後在 App Extension 裡添加這個 App Group 即可。
這樣,我們就可以用 NSUserDefaults 通過這個 App Group 交流數據了。
還記得我說過要拿出所有數據的前四條放到今日面板中展示嗎?下面我們就來實現這個功能:
當主應用的數據變化後就調用這個方法來更新快照數據。
下面我們主要來看 Today Extension 怎麼實現,首先看看這兩個方法:
其中第一個方法是系統告訴 Extension 需要更新了,當你更新完畢之後通過 block 回調告訴系統你完成了還有做了什麼,通常我們就告訴系統我們更新數據了即可(就是給 block 傳 NCUpdateResultNewData 枚舉項作為參數)。
其中第二個方法是返回一個內補大小,如果不實現,默認情況視圖左側會有一定的縮進。當然,蘋果還是希望你不要修改默認的內補~
然後我們實現數據的讀取:
P.S. 第三行寫錯了,不要管它
其實也很簡單,就是從 App Group 的配置裡拿出前 4 項的快照,然後更新一下 Table View 即可。這個方法在 viewDidLoad 或者 widgetPerformUpdateWithCompletionHandler: 中調用都可以。
到這我們看看效果,選中 Today Extension 的那個 Scheme 點擊調試按鈕,彈出下面的對話框:
選擇我們的主程序,點擊 『Run』。
App Extension 調起主程序並執行動作
當我們的 Todo List 是空的情況下,我們希望在今日面板裡展示一個按鈕,點擊後可以快速進入創建 Todo 的界面,就像這樣:
由於我們訪問不了主程序的代碼,所以只剩下一條路可以選,那就是 URL Scheme。
首先,我們給主程序注冊一個 URL Scheme:
然後響應按鈕點擊:
由於 App Extension 訪問不了 UIApplication,因此不能用它的 openURL:,但是我們可以用 extensionContext 來打開 URL,用法和效果是一樣的。
回到主程序,我們處理 URL 的打開:
這裡我用 Notification 的方式告知指定 View Controller 來執行相應動作,當然你也可以用你自己喜歡的方式,這裡最復雜也就是處理路由,現在也有很多方法實現,我這裡就不深究了。
下面看看效果(不好意思,圖沒做好,不動請在新窗口中打開):
好了,到這我們就基本打通主程序和 App Extension 的相互通信了,是不是也很簡單呢?
最後,一個小提醒
由於通知中心的界面是一大塊 UIVisualEffectView ,並且具體參數調整過,所以插件的背景色最好保持透明,主要文字顏色最好是白色,次要文字的顏色最好是 lightTextColor,這樣能適應毛玻璃下的 Vibrancy 效果。
今日面板每個插件的高度計算和 UITableView 自適應高度的計算方式一致,如果你沒有設置 preferredContentSize,或者把它設為 CGSizeZero 了,就表示你想采用自適應高度,那麼系統就會根據你設計的 Auto Layout 來確定適合的高度。如果你想這麼做的話,直接參考 UITableViewCell 在 iOS 8 以後自適應高度的方式即可。