從iOS 8起,就有了App Extension。Extension的種類至今也擴充到了19種,應用也很廣泛,值得重點關註起來。 Extension幾乎可以看做一個內嵌的獨立App,擁有獨立的BundleID、證書、概要配置文件、進程空間、沙盒等等。只是需要打包在App內,類似於寄生在宿主App內, ...
從iOS 8起,就有了App Extension。Extension的種類至今也擴充到了19種,應用也很廣泛,值得重點關註起來。
Extension幾乎可以看做一個內嵌的獨立App,擁有獨立的BundleID、證書、概要配置文件、進程空間、沙盒等等。只是需要打包在App內,類似於寄生在宿主App內,捆綁安裝。不過一旦安裝應用後,擴展可以由系統獨立調用,執行擴展內的代碼邏輯。
這篇只記錄之前開發Today Extension的過程中,值得註意的事項。其實大多都是通用的。
1.創建Extension和證書管理
在已創建的項目中,新建File->New->Target->Today Extension,確定名稱後,就可以在工程文件的Targets列表中,看到新建的extension。
在General欄可以看到,Extension有獨立的Identity內容,Bundle Identifier一般具有宿主應用的Bundle Identifier首碼。如果使用Xcode8的自動管理證書,並登錄了開發者賬號,可以在開發者中心看到自動創建的該AppID。
接著為該AppID創建Provisioning files即可。
2.MainInterface.storyboard和主要的類
新創建的Extension與App的主Target分別在不同的文件目錄下,彼此隔離。主界面、Info.plist和本地化文件等都是單獨管理的,所以可創建並修改InfoPlist.strings的本地化文件中CFBundleDisplayName欄位,單獨為擴展命名,如果不修改,將預設使用宿主App的顯示名稱。
擴展和宿主App是隔離的,不能使用彼此的類、框架、資源,如果想使用宿主的類庫或者資源文件等,最直接的辦法就是拷貝到當前Target或者增加需要Link的框架。不過,查看Extension的Build Settings,Prefix Header可以填寫宿主App的Prefix Header,並使用其引用頭文件中的巨集定義。
擴展預設使用了MainInterface.storyboard,並綁定了一個類TodayViewController,並已經創建了一個Hello World視圖,我們可以直接使用並修改。但是如果不想使用storyboard或者需要修改啟動類,可以將Info.plist文件中NSExtension字典的NSExtensionMainInterface項去掉,增加NSExtensionPrincipalClass項,value則為指定啟動類的類名,例如TodayViewController。
3.生命周期
擴展的視圖控制器與宿主的視圖控制器一樣,具有常見的生命周期方法。例如當第一次在下拉的“今日”列表中顯示本應用擴展,則會執行viewDidLoad方法,然後依次執行其他方法;消失和再次出現,則會調用disappear和appear系列方法;即將被銷毀時候,調用dealloc方法。
4.Today Extension的size
iOS10以後,重新規定了Today Extension的size。寬度是固定(例如在iPhone6上是359),所以無法改變;但是高度方面,提供了兩種模式:
NCWidgetDisplayModeCompact:固定高度,則為110
NCWidgetDisplayModeExpanded:可以變化的高度,區間為110~616
使用如下代碼可以修改模式:
if ([[UIDevice currentDevice].systemVersion floatValue] >= 10) {
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
}
我們可以在NCWidgetProviding協議的如下代理方法中,取到size的最大範圍:
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize NS_AVAILABLE_IOS(10_0);
用如下方法確定邊距:
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets NS_DEPRECATED_IOS(8_0, 10_0, "This method will not be called on widgets linked against iOS versions 10.0 and later.");
如果需要修改Widget展示的背景高度,可以修改屬性:self.preferredContentSize
5.數據共用
既然擴展與宿主App是隔離的,那麼數據共用就需要使用App Groups了。
在App主Target的Capabilities欄,找到App Groups項,開啟功能,並點擊“+”符號添加一個共用的數據容器名稱,例如group.xxx。然後會發現主Target和擴展Target目錄中都生成了一個entitlements類型文件,記錄了一個App Groups項。
這個共用的容器,就是存放擴展和宿主App共用的數據的空間。
為了正常編譯,還需要前往開發者中心,編輯主應用和擴展的AppID,開啟支持App Groups功能,類似於開啟推送功能。
配置完成後,就是使用了。不管是採用UserDefaults、Archive、CoreData、FMDB、LevelDB等哪種數據存儲或操作方式,只需要將路徑指向共用的容器路徑就可以。
例如使用UserDefaults方式:
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.xxx"]; [defaults setObject:@"xxxxx" forKey:@"phoneNum"];
只是打開了指定的UserDefaults對象,使用的實例方法為initWithSuiteName。
其他方式同理。
6.類庫和資源共用
前面提到,擴展需要使用宿主Target的類庫和資源,可以直接拷貝到擴展Target目錄下,但是這樣做會增加應用體積。
另一個方法,就是創建framework動態鏈接庫。新建File->New->Target->Framework,得到一個新的Target,與系統的框架類似,我們可以自由添加類文件封裝功能,只暴露public頭文件供調用。在Build Phases欄的Headers項中,將Project組的頭文件拖動到Public組即可。
framework也有Bundle ID,但是不需要關註,並且不需要配置證書。
需要註意的是,新建的動態鏈接庫,一般會自動創建同名的頭文件,可以直接在其中引用需要暴露的自定義類頭文件。但是如果刪除了該頭文件,可能會有如下警告:
warning: no umbrella header found for target 'OrderFoodTodayKit', module map will not be generated
另外一個需要特別註意的地方,因為我們創建的動態鏈接庫是需要提供給擴展使用的,而擴展不支持部分Api,所以該framework中就需要除去擴展不支持的部分Api。在framework的General欄的Deployment Info中,勾選Allow app extension API only。這樣的話,如果在framework中使用了擴展不支持的Api,編譯會報錯。
7.Today Extension調起宿主App
如果需要從Today Extension中調起宿主App,可以使用宿主App的Url Scheme方式:
[self.extensionContext openURL:[NSURL URLWithString:@"xxxx://xxxx"] completionHandler:nil];
以上就是開發過程中,需要註意的事項記錄。
完整的開發教程,推薦查閱:http://www.cocoachina.com/ios/20141023/10027.html