標籤:方法 選項 body 觸控螢幕 container init 介面 key pps
一、App Extension的介紹
App Extension可以讓你擴充你APP的自訂功能和內容,使使用者可以在與其他應用或者系統進行互動的時候去使用它。app extension即為本文所說的extension。extension並不是一個獨立的app,它有一個包含在app bundle中的獨立bundle,extension的bundle尾碼名是.appex。其生命週期也和普通app不同,這些後文將會詳述。extension不能單獨存在,必須有一個包含它的containing app。擴充(app Extension )是 iOS 8 中引入的一個非常重要的新特性
extension的啟用方式:不同的extension啟用方式也不同,有的extension需要使用者手動啟用而有的可以在任何應用裡被啟用,比如:Today中的widget需要在Today中啟用和關閉;Custom keyboard需要在設定中進行相關設定;Photo Editing需要在使用照片時在照片管理器中啟用或關閉;Storage Provider可以在選擇檔案時出現;Share和Action可以在任何應用裡被啟用,但前提是開發人員需要設定Activation Rules,以確定extension需要在合適出現。
我們平時看到的Widget、和QQ的share等等,都是App Extension,是一些例子:
幾個關鍵詞
extension point系統中支援extension的地區叫做extension point(擴充點),extension的類別也是據此區分的,iOS上共有Today、Share、Action、Photo Editing、Storage Provider、Custom keyboard幾種,其中Today中的extension又被稱為widget。每種extension point的使用方式和適合乾的活都不一樣,因此不存在通用的extension。
containing app
containing app我們可以把它理解為容器App,就像的share extension,容器app就是。儘管蘋果開放了extension,但是在iOS中extension並不能單獨存在,要想提交到AppStore,必須將extension包含在一個app中提交,並且app的實現部分不可為空,這個包含extension的app就叫containing app。extension會隨著containing app的安裝而安裝,同時隨著containing app的卸載而卸載。
host app我們可以把它理解為宿主的App,能夠調起extension的app被稱為host app,比如:Safari app 裡面網頁分享到, Safari就是 host app ; widget的host app就是Today。
二、 Extension的種類
我們可以在Xcode的File--->New--->Target裡面看到不同平台的Extension,包括iOS、watchOS、tvOS、macOS等等。這裡主要介紹iOS,主要包括以下幾種Extensions:
也可直接參考官方文檔
iOS 8 系統有 6 個支援擴充的系統地區,分別是 Today 、 Share 、 Action 、 Photo Editing 、 Storage Provider 、 Custom keyboard 。支援擴充的系統地區也被稱為擴充點。
Today Widget
Today擴充 可以快速擷取更新或者在通知中樞的近日視圖中執行一項快速任務。對於賽事比分,股票、天氣、快遞這類需要即時擷取的資訊,可以在通知中樞的Today 視圖中建立一個 Today 擴充實現。 Today 擴充又稱為 Widget 。
Today擴充
Share Extension
分享擴充,發布一個共用網站或者與其他應用共用內容,在 iOS 8 之前,使用者只有 Facebook,Twitter 等有限的幾個分享選項可以選擇。在 iOS 8 中,開發人員可以建立自訂的分享選項。
Share擴充
Action Extension
動作擴充,在另一個應用程式的上下文中操作或者查看內容, 在所有支援的擴充點中擴充性最強的一個。它可以實現轉換另一個 app 上下文中的內容。蘋果在 WWDC 大會上示範了一個 Bing 翻譯動作擴充,它可以將在 Safari 中選中的文本翻譯成不同的語言。
Action擴充注意??:
Host App(照片、Safari、郵件、語音等)分享菜單第一行是:
Share Extension ;第二行是:
Action Extension;
Photo Editing
圖片編輯擴充,在照片app中編輯照片或者視頻,在 iOS 8 之前,如果你想為你的照片添加一個特殊的濾鏡,你需要進入第三方 app 中,這個過程是相當繁瑣的。在 iOS 8 中,你可以直接在 Photos 中使用第三方 app ,如 Instagram , VSCO cam 、 Aviary 提供的 Photo Editing 擴充完成對圖片的編輯,而無需離開當前的 app 。
Photo Editing擴充
Document Provider
Document Provider 讓跨多個檔案儲存體服務之間的管理變得更簡單。類似 Dropbox 、 Google Drive 等儲存供應商通過在 iOS 8 中提供一個 Document Provider 擴充, app 直接可以使用這些擴充檢索和隱藏檔而不再需要建立不必要的拷貝。
Document Provider擴充
Custom Keyboard
鍵盤擴充,例如第三方的鍵盤,搜狗IME,百度IME等。蘋果公司在 2007 年率先推出了觸控螢幕鍵盤,但一直沒多大改進。在這一方面, Android 則將鍵盤許可權開放給了第三方開發人員,所以出現了許多像 Swype , SwiftKey 等優秀的鍵盤IME。在 iOS 8 中,蘋果終於將鍵盤許可權開發給了第三方開發人員,自訂鍵盤IME可以讓使用者在整個系統範圍內使用。
Custom Keyboard擴充
以下是iOS 9和之後中新增擴充
1.Audio Unit Extension:音頻單元擴充
2.Broadcast UI Extension:廣播UI 擴充
3.Broadcast Upload Extension:廣播上傳擴充
4.Call Directory Extension:呼叫目錄擴充
5.Content Blocker Extension:內容攔截器擴充
6.iMessage Extension:訊息的擴充
7.Intents Extension:Intents擴充
8.Intents UI Extension:Intents UI擴充
9.Notification Content Extension:通知內容擴充
10.Notification Service Extension:通知服務擴充
11.Shared Links Extension:分享連結擴充功能
12.Spotlight Index Extension:Spotlight 索引擴充
13.Sticker Pack Extension:貼紙包擴充
三、App Extensions的生命週期
以下是蘋果官方提供的圖片:
1.使用者選擇要使用的App extension
2.系統啟動App Extension
3.App Extension 代碼運行
4.運行完之後系統kill掉App Extension
這就是App Extension的生命週期,舉個例子:
一個Share Extension,在圖庫裡面你選擇了一張圖片,然後點擊分享,選擇你的Share Extension(第一步),此時系統會啟動你的Share Extension(第二步)。然後你將選擇的圖片分享到指定的程式(例如的發送給朋友)(第三步)。接下來分享頁面關閉,系統kill掉了Share Extension。
四、App Extension的通訊方式
App Extension主要的通訊是和他的host app(例如的Share Extension和)
這個展示的就是正在啟動並執行App Extension、host app和containing app之間的關係。可以看出:Containing App和app Extension並沒有直接的溝通。甚至有的時候Containing app可以不運行,而App Extension直接運行。Containing app和Host app沒有任何的溝通。
在一個典型的request/response中,系統開啟代表host app(圖庫)的extension(分享的share extension),把host app提供的資料(圖片和選擇的好友)輸送到extension的context,然後extension展示介面,提供一些功能任務(例如的分享到朋友)。
還有一種是app extension可以直接和他的containing app溝通:
例如Today Widget,可以直接告訴系統開啟他的Containing app,只需要調用NSExtensionContext的openURL:CompletionHandler:方法即可。
app extension和containing app可以共同讀寫一個被稱為Shared resources的儲存地區,這是通過App Groups實現的。
這裡需要注意的是,在iOS 8.0中,只有Today Extension才支援直接調用NSExtensionContext的openURL:completionHandler:開啟URL連結;Share/Action Extension要想實現URL Scheme,只能建立一個Sink UIWebVew對URL進行loadRequest實現曲線救國(所謂“Sink”是指隱而不顯,例如frame=CGRectZero)現在這個方法也不行了(可見蘋果是不推薦開啟主app的)但是如果你不怕蘋果審核不通過 或者 下架 也可以用以下方法開啟:
// UIWebView *webView = [[UIWebView alloc]init];// webView.hidden = YES;// NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:@"weixin://"]];// [webView loadRequest:request];// [self.view addSubview:webView]; //這個方法已經打不開了 UIResponder* responder = self; while ((responder = [responder nextResponder]) != nil) { if ([responder respondsToSelector:@selector(openURL:)] == YES) { [responder performSelector:@selector(openURL:) withObject:[NSURL URLWithString:[NSString stringWithFormat:@"weixin://"]]]; } }
stackoverflow 裡面有關於 Share Extension to open containing app 相關的解答
五、App Groups 實現資料共用這是iOS8新開放的功能,在OS X上早就可用了。它主要用於同一group下的app共用同一份讀寫空間,以實現資料共用。extension和containing app共同讀寫一份資料是很合理的需求,比如系統的股市應用,widget和app中都需要展示幾個公司的股票資料,這就可以通過App Groups實現。
功能開啟 為了便於後續操作,請先確保你的開發人員帳號在Xcode上處於登入狀態。(或者在開發人員帳號中先建立好app group id )
一、在app中開啟App Groups位於:
- TARGETS-->AppExtensionDemo-->Capabilities-->App Groups
找到以後,將App Groups右上方的開關開啟,然後選擇添加groups,比如我的是group.wangzz,當然這是為了測試隨便起得名字,正規點得命名規則應該是:group.com.company.app。 添加成功以後如所示:
二、在extension中也要開啟我建立的是widget,target名稱為TodayExtension,對應的App Groups位於:
- TARGETS-->TodayExtension-->Capabilities-->App Groups
開啟方式和app中一樣,需要注意的是必須保證這裡地App Groups名稱和app中的相同,即為group.wangzz。
三、extension和containing app資料共用
App Groups給我們提供了同一group內app可以共同讀寫的地區,可以通過以下方式實現資料共用:
3.1 通過NSUserDefaults共用資料
存資料通過以下方式向NSUserDefaults中儲存資料:
- (void)saveTextByNSUserDefaults { NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"]; [shared setObject:_textField.text forKey:@"wangzz"]; [shared synchronize]; }
需要注意的是: 1.儲存資料的時候必須指明group id; 2.而且要注意NSUserDefaults能夠處理的資料只能是可plist化的對象,詳情見Property List Programming Guide。 3.為了防止出現資料同步問題,不要忘記調用[shared synchronize];
讀資料對應的讀取資料方式:
- (NSString *)readDataFromNSUserDefaults { NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"]; NSString *value = [shared valueForKey:@"wangzz"]; return value; }
3.2 通過NSFileManager共用資料 NSFileManager在iOS7提供了containerURLForSecurityApplicationGroupIdentifier方法,可以用來實現app group共用資料。
儲存資料
- (BOOL)saveTextByNSFileManager { NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"]; NSString *value = _textField.text; BOOL result = [value writeToURL:containerURL atomically:YES encoding:NSUTF8StringEncoding error:&err]; if (!result) { NSLog(@"%@",err); } else { NSLog(@"save value:%@ success.",value); } return result; }
讀資料
- (NSString *)readTextByNSFileManager { NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"]; NSString *value = [NSString stringWithContentsOfURL:containerURL encoding:NSUTF8StringEncoding error:&err]; return value; }
在這裡我試著儲存和讀取的是字串資料,但讀寫SQlite我相信也是沒問題的。
資料同步兩個應用共同讀取同一份資料,就會引發資料同步問題。WWDC2014的視頻中建議使用NSFileCoordination實現普通檔案的讀寫同步,而資料庫可以使用CoreData,Sqlite也支援同步。
四、extension和containing app代碼共用 和資料共用類似,extension和containing app很自然地會有一些商務邏輯上可以共用的代碼,這時可以通過iOS8中剛開放使用的framework實現。蘋果在App Extension Programming Guide中是這樣描述的: In iOS 8.0 and later, you can use an embedded framework to share code between your extension and its containing app. For example, if you develop image-processing code that you want both your Photo Editing extension and its containing app to share, you can put the code into a framework and embed it in both targets. 即將framework分別嵌入到extension和containing app的target中實現代碼共用。(意思是:需要分別要將framework分別copy到extension和containing app的main bundle中。) 也就是說代碼是不可以共用的 要想用containing app的代碼只有 在copy 一份。 以下自己思考的作為參考:參考extension和containing app資料共用,我試想能不能將framework只儲存一份放在App Groups地區?
4.1 copy framework到App Groups 在app初次開機的時候將framework放到App Groups地區:
- (BOOL)copyFrameworkFromMainBundleToAppGroup { NSFileManager *manager = [NSFileManager defaultManager]; NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; NSString *sorPath = [NSString stringWithFormat:@"%@/Dylib.framework",[[NSBundle mainBundle] bundlePath]]; NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path]; BOOL removeResult = [manager removeItemAtPath:desPath error:&err]; if (!removeResult) { NSLog(@"%@",err); } else { NSLog(@"remove success."); } BOOL copyResult = [[NSFileManager defaultManager] copyItemAtPath:sorPath toPath:desPath error:&err]; if (!copyResult) { NSLog(@"%@",err); } else { NSLog(@"copy success."); } return copyResult; }
4.2 使用framework:
- (BOOL)loadFrameworkInAppGroup { NSError *err = nil; NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path]; NSBundle *bundle = [NSBundle bundleWithPath:desPath]; BOOL result = [bundle loadAndReturnError:&err]; if (result) { Class root = NSClassFromString(@"Person"); if (root) { Person *person = [[root alloc] init]; if (person) { [person run]; } } } else { NSLog(@"%@",err); } return result; }
經過測試,竟然能夠載入成功。 需要說明的是,這裡只是說那麼用是可以成功載入framework,但還面臨不少問題,比如如果使用者在啟動app之前去使用extension,這時framework還沒有copy過去,怎麼處理;另外iOS的機制或者蘋果的審核是否允許這樣使用等。 在一切確定下來之前還是乖乖按文檔中的方式使用吧。
六、在App Extension中不可以做的事情
一個app extension不能有以下情況:
1.訪問sharedApplication對象。因此不能使用任何該對象的防範
2.使用任何標記NS_EXTENSION_UNAVAILABLE宏的API,或者類似的宏,或者不可用framework裡面的API,例如HealthKit framework不能用於app extensions
3.iOS裝置訪問相機或者麥克風(iMessage app可以訪問這些資源,只要在Info.plist裡面進行配置使用描述即可)
4.運行一個長時間的背景工作(根據不同平台而異)
5.使用AirDrop接收資料
iOS開發 之 App Extension 整體總結