iOS應(yīng)用之間的通信

前言

蘋果的沙盒機(jī)制

出于安全考慮恳蹲,iOS系統(tǒng)的沙盒機(jī)制規(guī)定每個(gè)應(yīng)用都只能訪問當(dāng)前沙盒目錄下面的文件(也有例外查辩,比如系統(tǒng)通訊錄能在用戶授權(quán)的情況下被第三方應(yīng)用訪問)骏全,這個(gè)規(guī)則把iOS系統(tǒng)的封閉性展現(xiàn)的淋漓盡致与斤。詳細(xì)可以參考我的iOS多線程技術(shù)的文章肪康。


image.png

iOS平臺應(yīng)用之間的通信

由于沙盒機(jī)制的存在,iOS應(yīng)用之間的通信就只能通過間接的方式撩穿。正如蘋果官方文檔 ## Inter-App Communication所說磷支。

Apps communicate only indirectly with other apps on a device. You can use AirDrop to share files and data with other apps. You can also define a custom URL scheme so that apps can send information to your app using URLs.

歸納起來由以下幾種方式:

  1. airDrop, 該方式經(jīng)常是蘋果設(shè)備之間傳遞文件使用,當(dāng)然你也可以通過該方式分享safari里面的網(wǎng)頁給另外一臺設(shè)備的safari食寡。
  2. 通過服務(wù)器來實(shí)現(xiàn)雾狈,跨應(yīng)用和跨設(shè)備之間的通信。比如:QQ通信錄助手冻河,QQ箍邮, 微信茉帅,新浪微博等等等。
  3. 系統(tǒng)級別的應(yīng)用锭弊,比如:通信錄堪澎,相冊,健康等由系統(tǒng)統(tǒng)一管理的數(shù)據(jù)的應(yīng)用味滞,第三方應(yīng)用可以通過在info.plist里面注冊說明(iOS9以后)用途樱蛤,調(diào)用系統(tǒng)API來調(diào)用獲取對應(yīng)的數(shù)據(jù),關(guān)于隱私數(shù)據(jù)蘋果審核越來越嚴(yán)格剑鞍,也就要求info.plist里面說明數(shù)據(jù)用途盡可能詳細(xì)具體昨凡,才更容易通過AppStore的審核。
  4. 通過自定義URL Scheme的形式蚁署,經(jīng)常用于一個(gè)應(yīng)用調(diào)用另外一個(gè)應(yīng)用便脊,比如:社交化方面的第三方登錄,分享(QQ, 微信光戈,微博等)哪痰。包括常用的第三方社交化框架ShareSDK、友盟也主要是基于此技術(shù)實(shí)現(xiàn)調(diào)用第三方APP進(jìn)行登錄或者分享等授權(quán)功能久妆。當(dāng)然開發(fā)者也可以自定義的形式來實(shí)現(xiàn)該部分功能晌杰,可以參考筆者文章:iOS進(jìn)階篇-應(yīng)用之間的跳轉(zhuǎn)
  5. 通過UIDocumentInteractionController或者是UIActivityViewController這倆個(gè)iOS SDK中封裝好的類在App之間發(fā)送數(shù)據(jù)蓝晒、分享數(shù)據(jù)和操作數(shù)據(jù)全度。該部分常用于跨應(yīng)用打開文件:比如通過QQ傳遞過來的PDF文件,用PDF閱讀器打來薪伏。
  6. 最后一種是App Extension,該部分功能是iOS8的SDK中提供拓展新特性能烂琴。
    可以實(shí)現(xiàn)跨APP應(yīng)用之間的操作和分享爹殊。

本篇文章將圍繞方式5和方式6展開介紹這方面的技術(shù),也就是蘋果原生提供的基于iOS SDK的分享技術(shù)监右。同時(shí)推薦倆篇蘋果開發(fā)者中心的文檔:Inter-App CommunicationDocument Interaction Programming Topics for iOS

Document Interaction

1.1 幾個(gè)基本概念

1.1.1 About Document Interaction

點(diǎn)擊以上鏈接可以打開官方文檔边灭,文檔說明了應(yīng)用可以通過制定的步驟向系統(tǒng)注冊自身可以支持的文件類型,以便于能夠用于打開其它應(yīng)用里面的文件健盒。

1.1.2 Uniform Type Identifier

簡稱UTI绒瘦,用于類型標(biāo)識。也可以參見如下鏈接:詳解蘋果提供的UTI(統(tǒng)一類型標(biāo)識符)扣癣。
概括來說:
第一惰帽、UTI的體系樹,如下圖官方文檔所示

uniform_type_identifier_2x.png

第二父虑、對于自己應(yīng)用UTI命名该酗,一般采用反域名的形式,類似于包名,盡可能的保證唯一呜魄,比如QQ采用的就是com.tencent.mqq悔叽。
WX20180313-154157.png

1.2 操作配置

我們需要在info.plist文件中,添加一個(gè)新的屬性CFBundleDocumentTypes(實(shí)際上輸入的是"Document types")爵嗅,這是一個(gè)數(shù)組類型的屬性娇澎,意思就是我們可以同時(shí)注冊多個(gè)類型。而針對數(shù)組中的每一個(gè)元素睹晒,都有許多屬性可以指定趟庄,詳細(xì)的屬性列表我們可以從官方文檔上找到: Core Foundation Keys ---- CFBundleDocumentTypes。這里列舉我們在做iOS開發(fā)時(shí)常用的屬性:
CFBundleTypeName("Icon File Name")
字符串類型伪很,指定某種類型的別名戚啥,也就是用來指代我們規(guī)定的類型的別稱,一般為了保持唯一性锉试,我們使用UTI來標(biāo)識猫十。
CFBundleTypeIconFiles
數(shù)組類型,包含指定的png圖標(biāo)的文件名键痛,指定代表某種類型的圖標(biāo)炫彩,而圖標(biāo)有具體的尺寸標(biāo)識:

Device Sizes
iPad 64 x 64 pixels, 320 x 320 pixels
iPhone and iPod touch 22 x 29 pixels, 44 x 58 pixels (high resolution)

LSItemContentTypes("Document Content Type UTIs")
數(shù)組類型,包含UTI字符串絮短,指定我們的應(yīng)用程序所有可以識別的類型集合
LSHandlerRank("Handler rank")
字符串類型,包含Owner,Default,Alternate,None四個(gè)可選值昨忆,指定對于某種類型的優(yōu)先權(quán)級別丁频,而Launcher Service會根據(jù)這個(gè)優(yōu)先級別來排列顯示的App的順序。優(yōu)先級別從高到低依次是Owner邑贴,Alternate,Default席里。None表示不接受這種類型。
以下配置可作為參考:

WX20180313-160927.png

WX20180313-161056.png

經(jīng)過以上配置之后拢驾,編譯之后奖磁,可以在工程中看到如下界面,表示配置成功繁疤。
WX20180313-161634.png

1.3 打開第三方應(yīng)用測試

特別注意該部分配置依據(jù)蘋果官方文檔是通過系統(tǒng)的launch service來完成的咖为,安裝成功之后,需要重啟手機(jī)稠腊,才能看到現(xiàn)象躁染。

1.3.1 打開QQ應(yīng)用的任何一張圖片

筆者采用的是某個(gè)聊天窗口的圖片,運(yùn)行如圖:


image.png

image.png

1.3.2 處理openURL的代理

之后iOSShare的應(yīng)用打開架忌,如下為代碼的運(yùn)行結(jié)果吞彤,之后開發(fā)者可以在對應(yīng)的openURL的代理中處理該圖片,實(shí)例代碼如下

#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation
{
    NSLog(@"ios9以下調(diào)用了OpenURL的接口。饰恕。挠羔。。埋嵌。破加。。莉恼。拌喉。。俐银。返回的內(nèi)容為=============================");
    NSLog(@"url = %@", url);
    NSLog(@"sourceApplication = %@", sourceApplication);
    NSLog(@"annotation = %@", annotation);
    return YES;
}

#else
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(nonnull NSDictionary<NSString *,id> *)options
{
    NSLog(@"ios9以上調(diào)用了OpenURL的接口尿背。。捶惜。田藐。。吱七。汽久。。踊餐。景醇。。返回的內(nèi)容為=============================");
    NSLog(@"url = %@", url);
    NSLog(@"options = %@", options);
    
    //新建立一個(gè)頁面用于預(yù)覽該圖片
    
    return YES;
}
#endif
image.png

1.3.3 到沙盒中一探究竟

打開沙盒結(jié)合刪除的log的輸出結(jié)構(gòu)們可以看出吝岭,實(shí)際上該部分內(nèi)容已經(jīng)完成了這張圖片的拷貝三痰,拷貝的地址為Documents目錄下的Inbox里面。


WX20180313-153914@2x.png

Share Extension

2.1 拓展的基本概述

擴(kuò)展( Extension )是 iOS 8 中引入的一個(gè)非常重要的新特性窜管。擴(kuò)展讓 app 之間的數(shù)據(jù)交互成為可能散劫。用戶可以在 app 中使用其他應(yīng)用提供的功能,而無需離開當(dāng)前的應(yīng)用幕帆。在 iOS 8 系統(tǒng)之前获搏,每一個(gè) app 在物理上都是彼此獨(dú)立的, app 之間不能互訪彼此的私有數(shù)據(jù)失乾。而在引入擴(kuò)展之后常熙,其他 app 可以與擴(kuò)展進(jìn)行數(shù)據(jù)交換≌萄铮基于安全和性能的考慮症概,每一個(gè)擴(kuò)展運(yùn)行在一個(gè)單獨(dú)的進(jìn)程中,它擁有自己的 bundle 早芭, bundle 后綴名是.appex 彼城。擴(kuò)展 bundle 必須包含在一個(gè)普通應(yīng)用的 bundle 的內(nèi)部。
iOS 8 系統(tǒng)有 6 個(gè)支持?jǐn)U展的系統(tǒng)區(qū)域,分別是 Today 募壕、 Share 调炬、 Action 、 Photo Editing 舱馅、 Storage Provider 缰泡、 Custom keyboard 。支持?jǐn)U展的系統(tǒng)區(qū)域也被稱為擴(kuò)展點(diǎn)代嗤。
iOS 9中新增擴(kuò)展
網(wǎng)絡(luò)擴(kuò)展
開發(fā)者可以通過改擴(kuò)展來實(shí)現(xiàn)自定義的VPN客戶端棘钞、透明的網(wǎng)絡(luò)代理客戶端以及實(shí)現(xiàn)動態(tài)的設(shè)備端網(wǎng)絡(luò)內(nèi)容過濾。
Safari擴(kuò)展
該擴(kuò)展可以讓用戶通過Safari的分享鏈接看到你的內(nèi)容干毅。又或者提供一個(gè)屏蔽列表宜猜,讓你的用戶使用你的App瀏覽Web內(nèi)容時(shí)屏蔽指定的內(nèi)容。
Spotlight擴(kuò)展
該擴(kuò)展可以對App內(nèi)的數(shù)據(jù)進(jìn)行索引硝逢,并且可以在不重啟App的情況下重建數(shù)據(jù)索引姨拥。
Audio Unit擴(kuò)展
該擴(kuò)展允許App提供類似于GarageBand,Logic等App提供的樂器演奏渠鸽,音頻特效叫乌,聲音合成功能。

2.1.1 拓展里面基本結(jié)構(gòu)

一個(gè)支持?jǐn)U展的系統(tǒng)區(qū)域叫做一個(gè)extension point(擴(kuò)展點(diǎn))徽缚。每個(gè)擴(kuò)展點(diǎn)的擴(kuò)展都有自己獨(dú)有的使用方法和API憨奸。你可以根據(jù)你的需求來選擇不同的擴(kuò)展。官方API里面提出了一個(gè)名詞叫:Host app凿试,我們可以把它理解為宿主的App也就是提供應(yīng)用擴(kuò)展界面顯示或者功能的App膀藐。還有一個(gè)container app,我們可以把它理解為容器App红省,就像下圖的微信share extension,容器app就是微信国觉。

image.png

擴(kuò)展和app不同吧恃,擴(kuò)展無法單獨(dú)上架AppStore。盡管你必須使用個(gè)app來包含并且分發(fā)你的extension麻诀,extension也是一個(gè)單獨(dú)的二進(jìn)制文件痕寓,獨(dú)立于用于傳遞和分發(fā)的container app。

你可以通過File--->New --->Target來創(chuàng)建Extension蝇闭,它和其他的target一樣呻率,它和你的app project組合成為一個(gè)產(chǎn)品。一個(gè)app可以有一個(gè)擴(kuò)展呻引,也可以有多個(gè)擴(kuò)展礼仗。最好的創(chuàng)建擴(kuò)展的方式就是通過Xcode提供的Extension種類選擇自己需要的來創(chuàng)建,里面包含了必要的API以及方法實(shí)現(xiàn)。
如果你想讓用戶去使用你的擴(kuò)展元践,那么就需要吧你的containing app發(fā)布到AppStore韭脊,當(dāng)用戶安裝了你的Containing app,擴(kuò)展也就安裝了单旁。不同的擴(kuò)展啟動的方式也不一樣沪羔,例如Today Extension,你需要Widget來展示到你的通知中心象浑。擴(kuò)展也不要亂用蔫饰,擴(kuò)展的最佳用戶體驗(yàn)從來都是希望用戶操作更精簡、更快速愉豺,并且專注于單個(gè)任務(wù)篓吁。

2.1.2 shareExtension的生命周期

先上圖,估計(jì)你已經(jīng)看到了好多次這張圖粒氧,恭喜你這次又看到了越除,因?yàn)檫@個(gè)是蘋果官方提供的圖片。


image

1.用戶選擇要使用的App extension
2.系統(tǒng)啟動App Extension
3.App Extension 代碼運(yùn)行
4.運(yùn)行完之后系統(tǒng)kill掉App Extension
這就是App Extension的生命周期外盯,舉個(gè)例子:
一個(gè)Share Extension摘盆,在圖庫里面你選擇了一張圖片,然后點(diǎn)擊分享饱苟,選擇你的Share Extension(第一步)孩擂,此時(shí)系統(tǒng)會啟動你的Share Extension(第二步)。然后你將選擇的圖片分享到指定的程序(例如微信的發(fā)送給朋友)(第三步)箱熬。接下來分享頁面關(guān)閉类垦,系統(tǒng)kill掉了Share Extension。

2.1.3 App Extension的通信方式

App Extension主要的通信是和他的host app(例如微信的Share Extension和微信)城须,來自host app的請求和extension的response蚤认。下圖你應(yīng)該也很熟悉(app 擴(kuò)展直接和host app溝通):

image

這個(gè)展示的就是正在運(yùn)行的App Extension、host app和containing app之間的關(guān)系糕伐∨樽粒可以看出:Containing App和app Extension并沒有直接的溝通。甚至有的時(shí)候Containing app可以不運(yùn)行良瞧,而App Extension直接運(yùn)行陪汽。Containing app和Host app沒有任何的溝通。

在一個(gè)典型的request/response中褥蚯,系統(tǒng)打開代表host app(圖庫)的extension(微信分享的share extension)挚冤,把host app提供的數(shù)據(jù)(圖片和選擇的好友)輸送到extension的context,然后extension展示界面赞庶,提供一些功能任務(wù)(例如微信的分享到朋友)训挡。

還有一種是app extension可以直接和他的containing app溝通:

image

例如Today Widget澳骤,可以直接告訴系統(tǒng)打開他的Containing app,只需要調(diào)用NSExtensionContext的openURL:CompletionHandler:方法即可舍哄。

2.1.4 在App Extension中不可以做的事情

一個(gè)app extension不能有以下情況:

1.訪問sharedApplication對象宴凉。因此不能使用任何該對象的防范

2.使用任何標(biāo)記NS_EXTENSION_UNAVAILABLE宏的API,或者類似的宏表悬,或者不可用framework里面的API弥锄,例如HealthKit framework不能用于app extensions

3.iOS設(shè)備訪問相機(jī)或者麥克風(fēng)(iMessage app可以訪問這些資源,只要在Info.plist里面進(jìn)行配置使用描述即可)

4.運(yùn)行一個(gè)長時(shí)間的后臺任務(wù)(根據(jù)不同平臺而異)

5.使用AirDrop接收數(shù)據(jù)

2.2 上手和實(shí)踐

注:擴(kuò)展不能單獨(dú)創(chuàng)建蟆沫,必須依賴于應(yīng)用工程項(xiàng)目籽暇,因此如果你還沒有創(chuàng)建一個(gè)應(yīng)用工程,先去創(chuàng)建一個(gè)饭庞。

2.2.1 打開項(xiàng)目設(shè)置戒悠,在TARGETS側(cè)欄地下點(diǎn)擊“+”號來創(chuàng)建一個(gè)新的Target,如圖:
image
2.2.2 然后選擇”iOS” -> “Application Extension” -> “Share Extension”舟山,點(diǎn)擊“Next”绸狐。如圖:
image
2.2.3 配置Share Extension
字段 描述
Bundle display name 擴(kuò)展的顯示名稱,默認(rèn)跟你的項(xiàng)目名稱相同累盗,可以通過修改此字段來控制擴(kuò)展的顯示名稱寒矿。
NSExtension 擴(kuò)展描述字段,用于描述擴(kuò)展的屬性若债、設(shè)置等符相。作為一個(gè)擴(kuò)展項(xiàng)目必須要包含此字段。
NSExtensionAttributes 擴(kuò)展屬性集合字段蠢琳。用于描述擴(kuò)展的屬性啊终。
NSExtensionActivationRule 激活擴(kuò)展的規(guī)則。默認(rèn)為字符串“TRUEPREDICATE”傲须,表示在分享菜單中一直顯示該擴(kuò)展蓝牲。可以將類型改為Dictionary類型泰讽,然后添加以下字段: NSExtensionActivationSupportsAttachmentsWithMaxCount NSExtensionActivationSupportsAttachmentsWithMinCount NSExtensionActivationSupportsImageWithMaxCount NSExtensionActivationSupportsMovieWithMaxCount NSExtensionActivationSupportsWebPageWithMaxCount NSExtensionActivationSupportsWebURLWithMaxCount
NSExtensionMainStoryboard 設(shè)置主界面的Storyboard搞旭,如果不想使用storyboard,也可以使用NSExtensionPrincipalClass指定自定義UIViewController子類名
NSExtensionPointIdentifier 擴(kuò)展標(biāo)識菇绵,在分享擴(kuò)展中為:com.apple.share-services
NSExtensionPrincipalClass 自定義UI的類名
NSExtensionActivationSupportsAttachmentsWithMaxCount 附件最多限制,為數(shù)值類型镇眷。附件包括File咬最、Image和Movie三大類,單一欠动、混選總量不超過指定數(shù)量
NSExtensionActivationSupportsAttachmentsWithMinCount 附件最少限制永乌,為數(shù)值類型惑申。當(dāng)設(shè)置NSExtensionActivationSupportsAttachmentsWithMaxCount時(shí)生效,默認(rèn)至少選擇1個(gè)附件翅雏,分享菜單中才顯示擴(kuò)展插件圖標(biāo)圈驼。
NSExtensionActivationSupportsFileWithMaxCount 文件最多限制,為數(shù)值類型望几。文件泛指除Image/Movie之外的附件绩脆,例如【郵件】附件、【語音備忘錄】等橄抹。單一靴迫、混選均不超過指定數(shù)量。
NSExtensionActivationSupportsImageWithMaxCount 圖片最多限制楼誓,為數(shù)值類型玉锌。單一、混選均不超過指定數(shù)量疟羹。
NSExtensionActivationSupportsMovieWithMaxCount 視頻最多限制主守,為數(shù)值類型。單一榄融、混選均不超過指定數(shù)量参淫。
NSExtensionActivationSupportsText 是否支持文本類型,布爾類型剃袍,默認(rèn)不支持黄刚。如【備忘錄】的分享
NSExtensionActivationSupportsWebURLWithMaxCount Web鏈接最多限制,為數(shù)值類型民效。默認(rèn)不支持分享超鏈接憔维,需要自己設(shè)置一個(gè)數(shù)值。
NSExtensionActivationSupportsWebPageWithMaxCount Web頁面最多限制畏邢,為數(shù)值類型业扒。默認(rèn)不支持Web頁面分享,需要自己設(shè)置一個(gè)數(shù)值舒萎。
image.png

我們只需要關(guān)注以下幾個(gè)字段的設(shè)置:

字段 描述
Bundle display name 擴(kuò)展的顯示名稱程储,默認(rèn)跟你的項(xiàng)目名稱相同,可以通過修改此字段來控制擴(kuò)展的顯示名稱臂寝。
NSExtension 擴(kuò)展描述字段章鲤,用于描述擴(kuò)展的屬性、設(shè)置等咆贬。作為一個(gè)擴(kuò)展項(xiàng)目必須要包含此字段败徊。
NSExtensionAttributes 擴(kuò)展屬性集合字段。用于描述擴(kuò)展的屬性掏缎。
NSExtensionActivationRule 激活擴(kuò)展的規(guī)則皱蹦。默認(rèn)為字符串“TRUEPREDICATE”煤杀,表示在分享菜單中一直顯示該擴(kuò)展』Σ福可以將類型改為Dictionary類型沈自,然后添加以下字段: NSExtensionActivationSupportsAttachmentsWithMaxCount NSExtensionActivationSupportsAttachmentsWithMinCount NSExtensionActivationSupportsImageWithMaxCount NSExtensionActivationSupportsMovieWithMaxCount NSExtensionActivationSupportsWebPageWithMaxCount NSExtensionActivationSupportsWebURLWithMaxCount
NSExtensionMainStoryboard 設(shè)置主界面的Storyboard,如果不想使用storyboard辜妓,也可以使用NSExtensionPrincipalClass指定自定義UIViewController子類名
NSExtensionPointIdentifier 擴(kuò)展標(biāo)識枯途,在分享擴(kuò)展中為:com.apple.share-services
NSExtensionPrincipalClass 自定義UI的類名
NSExtensionActivationSupportsAttachmentsWithMaxCount 附件最多限制,為數(shù)值類型嫌拣。附件包括File柔袁、Image和Movie三大類,單一异逐、混選總量不超過指定數(shù)量
NSExtensionActivationSupportsAttachmentsWithMinCount 附件最少限制捶索,為數(shù)值類型。當(dāng)設(shè)置NSExtensionActivationSupportsAttachmentsWithMaxCount時(shí)生效灰瞻,默認(rèn)至少選擇1個(gè)附件腥例,分享菜單中才顯示擴(kuò)展插件圖標(biāo)。
NSExtensionActivationSupportsFileWithMaxCount 文件最多限制酝润,為數(shù)值類型燎竖。文件泛指除Image/Movie之外的附件,例如【郵件】附件要销、【語音備忘錄】等构回。單一、混選均不超過指定數(shù)量疏咐。
NSExtensionActivationSupportsImageWithMaxCount 圖片最多限制纤掸,為數(shù)值類型。單一浑塞、混選均不超過指定數(shù)量借跪。
NSExtensionActivationSupportsMovieWithMaxCount 視頻最多限制,為數(shù)值類型酌壕。單一掏愁、混選均不超過指定數(shù)量。
NSExtensionActivationSupportsText 是否支持文本類型卵牍,布爾類型果港,默認(rèn)不支持。如【備忘錄】的分享
NSExtensionActivationSupportsWebURLWithMaxCount Web鏈接最多限制糊昙,為數(shù)值類型京腥。默認(rèn)不支持分享超鏈接,需要自己設(shè)置一個(gè)數(shù)值溅蛉。
NSExtensionActivationSupportsWebPageWithMaxCount Web頁面最多限制公浪,為數(shù)值類型。默認(rèn)不支持Web頁面分享船侧,需要自己設(shè)置一個(gè)數(shù)值欠气。

對于不同的應(yīng)用里面有可能出現(xiàn)只允許接受某種類型的內(nèi)容,那么Share Extension就不能一直出現(xiàn)在分享菜單中镜撩,因?yàn)椴煌膽?yīng)用提供的分享內(nèi)容不一樣预柒,這就需要通過設(shè)置NSExtensionActivationRule字段來決定Share Extension是否顯示。例如袁梗,只想接受其他應(yīng)用分享鏈接到自己的應(yīng)用宜鸯,那么可以通過下面的步驟來設(shè)置:
將NSExtensionActivationRule字段類型由String改為Dictionary。
展開NSExtensionActivationRule字段遮怜,創(chuàng)建其子項(xiàng)NSExtensionActivationSupportsWebURLWithMaxCount淋袖,并設(shè)置一個(gè)限制數(shù)量。
調(diào)整后如下圖所示:


image
2.2.4 處理Share Extension中的數(shù)據(jù)

其實(shí)在Share Extension中默認(rèn)都會有一個(gè)數(shù)據(jù)展現(xiàn)的UI界面锯梁。該界面繼承SLComposeServiceViewController這個(gè)類型即碗,如:

@interface ShareViewController : SLComposeServiceViewController

@end

其展現(xiàn)效果,如圖:

image

頂部包括了標(biāo)題陌凳、取消(Cancel)按鈕和提交(Post)按鈕剥懒。然后下面跟著左邊就是一個(gè)文本編輯框,右邊就是一個(gè)圖片顯示控件合敦。那么初橘,每當(dāng)用戶點(diǎn)擊取消按鈕或者提交按鈕時(shí),都會分別觸發(fā)下面的方法:

/**
 *  點(diǎn)擊取消按鈕
 */
- (void)didSelectCancel
{
    [super didSelectCancel];
}

/**
 *  點(diǎn)擊提交按鈕
 */
- (void)didSelectPost
{
    // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.

    // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
    [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
}

在這兩個(gè)方法里面可以進(jìn)行一些自定義的操作充岛。一般情況下保檐,當(dāng)用戶點(diǎn)擊提交按鈕的時(shí)候,擴(kuò)展要做的事情就是要把數(shù)據(jù)取出來裸准,并且放入一個(gè)與Containing App(容器程序展东,盡管蘋果開放了Extension,但是在iOS中extension并不能單獨(dú)存在炒俱,要想提交到AppStore盐肃,必須將Extension包含在一個(gè)App中提交,并且App的實(shí)現(xiàn)部分不能為空,這個(gè)包含Extension的App就叫Containing app权悟。Extension會隨著Containing App的安裝而安裝砸王,同時(shí)隨著ContainingApp的卸載而卸載。)共享的數(shù)據(jù)介質(zhì)中(包括NSUserDefault峦阁、Sqlite谦铃、CoreData),要跟容器程序進(jìn)行數(shù)據(jù)交互需要借助AppGroups服務(wù)榔昔,下面的章節(jié)會對這塊進(jìn)行詳細(xì)說明驹闰。下面先來看看怎么獲取擴(kuò)展中的數(shù)據(jù)瘪菌。

在ShareExtension中,UIViewController包含一個(gè)extensionContext這樣的上下文對象:

@interface UIViewController(NSExtensionAdditions) <NSExtensionRequestHandling>

// Returns the extension context. Also acts as a convenience method for a view controller to check if it participating in an extension request.
@property (nullable, nonatomic,readonly,strong) NSExtensionContext *extensionContext NS_AVAILABLE_IOS(8_0);

@end

通過操作它就可以獲取到分享的數(shù)據(jù)嘹朗,返回宿主應(yīng)用的界面等操作师妙。我們可以先看一下extensionContext的定義。

NS_CLASS_AVAILABLE(10_10, 8_0)
@interface NSExtensionContext : NSObject

// The list of input NSExtensionItems associated with the context. If the context has no input items, this array will be empty.
@property(readonly, copy, NS_NONATOMIC_IOSONLY) NSArray *inputItems;

// Signals the host to complete the app extension request with the supplied result items. The completion handler optionally contains any work which the extension may need to perform after the request has been completed, as a background-priority task. The `expired` parameter will be YES if the system decides to prematurely terminate a previous non-expiration invocation of the completionHandler. Note: calling this method will eventually dismiss the associated view controller.
- (void)completeRequestReturningItems:(nullable NSArray *)items completionHandler:(void(^ __nullable)(BOOL expired))completionHandler;

// Signals the host to cancel the app extension request, with the supplied error, which should be non-nil. The userInfo of the NSError will contain a key NSExtensionItemsAndErrorsKey which will have as its value a dictionary of NSExtensionItems and associated NSError instances.
- (void)cancelRequestWithError:(NSError *)error;

// Asks the host to open an URL on the extension's behalf
- (void)openURL:(NSURL *)URL completionHandler:(void (^ __nullable)(BOOL success))completionHandler;

@end

// Key in userInfo. Value is a dictionary of NSExtensionItems and associated NSError instances.
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionItemsAndErrorsKey NS_AVAILABLE(10_10, 8_0);

// The host process will enter the foreground
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionHostWillEnterForegroundNotification NS_AVAILABLE_IOS(8_2);

// The host process did enter the background
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionHostDidEnterBackgroundNotification NS_AVAILABLE_IOS(8_2);

// The host process will resign active status (stop receiving events), the extension may be suspended
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionHostWillResignActiveNotification NS_AVAILABLE_IOS(8_2);

// The host process did become active (begin receiving events)
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionHostDidBecomeActiveNotification NS_AVAILABLE_IOS(8_2);
字段 說明
inputItems 該數(shù)組存儲著容器應(yīng)用傳入給NSExtensionContext的NSExtensionItem數(shù)組屹培。其中每個(gè)NSExtensionItem標(biāo)識了一種類型的數(shù)據(jù)默穴。要獲取數(shù)據(jù)就要從這個(gè)屬性入手。
completeRequestReturningItems:completionHandler: 通知宿主程序的擴(kuò)展已完成請求褪秀。調(diào)用此方法后蓄诽,擴(kuò)展UI會關(guān)閉并返回容器程序中。其中的items就是返回宿主程序的數(shù)據(jù)項(xiàng)媒吗。
cancelRequestWithError: 通知宿主程序的擴(kuò)展已取消請求仑氛。調(diào)用此方法后,擴(kuò)展UI會關(guān)閉并返回容器程序中蝴猪。其中error為錯(cuò)誤的描述信息调衰。
NSExtensionItemsAndErrorsKey NSExtensionItem的userInfo屬性中對應(yīng)的錯(cuò)誤信息鍵名。

NSExtensionContext的結(jié)構(gòu)比較簡單自阱,包含一個(gè)屬性和三個(gè)方法嚎莉。其說明如下:

字段 說明
inputItems 該數(shù)組存儲著容器應(yīng)用傳入給NSExtensionContext的NSExtensionItem數(shù)組。其中每個(gè)NSExtensionItem標(biāo)識了一種類型的數(shù)據(jù)沛豌。要獲取數(shù)據(jù)就要從這個(gè)屬性入手趋箩。
completeRequestReturningItems:completionHandler: 通知宿主程序的擴(kuò)展已完成請求。調(diào)用此方法后加派,擴(kuò)展UI會關(guān)閉并返回容器程序中叫确。其中的items就是返回宿主程序的數(shù)據(jù)項(xiàng)。
cancelRequestWithError: 通知宿主程序的擴(kuò)展已取消請求芍锦。調(diào)用此方法后竹勉,擴(kuò)展UI會關(guān)閉并返回容器程序中。其中error為錯(cuò)誤的描述信息娄琉。
NSExtensionItemsAndErrorsKey NSExtensionItem的userInfo屬性中對應(yīng)的錯(cuò)誤信息鍵名次乓。
字段 說明
NSExtensionHostWillEnterForegroundNotification 宿主程序?qū)⒁祷厍芭_通知
NSExtensionHostDidEnterBackgroundNotification 宿主程序進(jìn)入后臺通知
NSExtensionHostWillResignActiveNotification 宿主程序?qū)⒁粧炱鹜ㄖ?/td>
NSExtensionHostDidBecomeActiveNotification 宿主程序被激活通知

類的下面還定義了一些通知,這些通知都是跟宿主程序的行為相關(guān)孽水,在設(shè)計(jì)擴(kuò)展的時(shí)候可以根據(jù)這些通知來進(jìn)行對應(yīng)的操作票腰。其說明如下:

字段 說明
NSExtensionHostWillEnterForegroundNotification 宿主程序?qū)⒁祷厍芭_通知
NSExtensionHostDidEnterBackgroundNotification 宿主程序進(jìn)入后臺通知
NSExtensionHostWillResignActiveNotification 宿主程序?qū)⒁粧炱鹜ㄖ?/td>
NSExtensionHostDidBecomeActiveNotification 宿主程序被激活通知

從上面的定義可以看出除了文本內(nèi)容驱闷,其他類型的內(nèi)容都是作為附件存儲的者娱,而附件又是封裝在一個(gè)叫NSItemProvider的類型中堡赔,其定義如下:

typedef void (^NSItemProviderCompletionHandler)(__nullable id <NSSecureCoding> item, NSError * __null_unspecified error);
typedef void (^NSItemProviderLoadHandler)(__null_unspecified NSItemProviderCompletionHandler completionHandler, __null_unspecified Class expectedValueClass, NSDictionary * __null_unspecified options);

// An NSItemProvider is a high level abstraction for file-like data objects supporting multiple representations and preview images.
NS_CLASS_AVAILABLE(10_10, 8_0)
@interface NSItemProvider : NSObject <NSCopying>

// Initialize an NSItemProvider with a single handler for the given item.
- (instancetype)initWithItem:(nullable id <NSSecureCoding>)item typeIdentifier:(nullable NSString *)typeIdentifier NS_DESIGNATED_INITIALIZER;

// Initialize an NSItemProvider with load handlers for the given file URL, and the file content.
- (nullable instancetype)initWithContentsOfURL:(null_unspecified NSURL *)fileURL;

// Sets a load handler block for a specific type identifier. Handlers are invoked on demand through loadItemForTypeIdentifier:options:completionHandler:. To complete loading, the implementation has to call the given completionHandler. Both expectedValueClass and options parameters are derived from the completionHandler block.
- (void)registerItemForTypeIdentifier:(NSString *)typeIdentifier loadHandler:(NSItemProviderLoadHandler)loadHandler;

// Returns the list of registered type identifiers
@property(copy, readonly, NS_NONATOMIC_IOSONLY) NSArray *registeredTypeIdentifiers;

// Returns YES if the item provider has at least one item that conforms to the supplied type identifier.
- (BOOL)hasItemConformingToTypeIdentifier:(NSString *)typeIdentifier;

// Loads the best matching item for a type identifier. The client's expected value class is automatically derived from the blocks item parameter. Returns an error if the returned item class does not match the expected value class. Item providers will perform simple type coercions (eg. NSURL to NSData, NSURL to NSFileWrapper, NSData to UIImage).
- (void)loadItemForTypeIdentifier:(NSString *)typeIdentifier options:(nullable NSDictionary *)options completionHandler:(nullable NSItemProviderCompletionHandler)completionHandler;

@end

// Common keys for the item provider options dictionary.
FOUNDATION_EXTERN NSString * __null_unspecified const NSItemProviderPreferredImageSizeKey NS_AVAILABLE(10_10, 8_0); // NSValue of CGSize or NSSize, specifies image size in pixels.

@interface NSItemProvider(NSPreviewSupport)

// Sets a custom preview image handler block for this item provider. The returned item should preferably be NSData or a file NSURL.
@property(nullable, copy, NS_NONATOMIC_IOSONLY) NSItemProviderLoadHandler previewImageHandler NS_AVAILABLE(10_10, 8_0);

// Loads the preview image for this item by either calling the supplied preview block or falling back to a QuickLook-based handler. This method, like loadItemForTypeIdentifier:options:completionHandler:, supports implicit type coercion for the item parameter of the completion block. Allowed value classes are: NSData, NSURL, UIImage/NSImage.
- (void)loadPreviewImageWithOptions:(null_unspecified NSDictionary *)options completionHandler:(null_unspecified NSItemProviderCompletionHandler)completionHandler NS_AVAILABLE(10_10, 8_0);

@end

// Keys used in property list items received from or sent to JavaScript code

// If JavaScript code passes an object to its completionFunction, it will be placed into an item of type kUTTypePropertyList, containing an NSDictionary, under this key.
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionJavaScriptPreprocessingResultsKey NS_AVAILABLE(10_10, 8_0);

// Arguments to be passed to a JavaScript finalize method should be placed in an item of type kUTTypePropertyList, containing an NSDictionary, under this key.
FOUNDATION_EXTERN NSString * __null_unspecified const NSExtensionJavaScriptFinalizeArgumentKey NS_AVAILABLE_IOS(8_0);

// Errors

// Constant used by NSError to distinguish errors belonging to the NSItemProvider domain
FOUNDATION_EXTERN NSString * __null_unspecified const NSItemProviderErrorDomain NS_AVAILABLE(10_10, 8_0);

// NSItemProvider-related error codes
typedef NS_ENUM(NSInteger, NSItemProviderErrorCode) {
    NSItemProviderUnknownError                                      = -1,
    NSItemProviderItemUnavailableError                              = -1000,
    NSItemProviderUnexpectedValueClassError                         = -1100,
    NSItemProviderUnavailableCoercionError NS_AVAILABLE(10_11, 9_0) = -1200
} NS_ENUM_AVAILABLE(10_10, 8_0);
字段 說明
initWithItem:typeIdentifier: 初始化方法魁袜,item為附件的數(shù)據(jù)析恢,typeIdentifier是附件對應(yīng)的類型標(biāo)識,對應(yīng)UTI的描述咙轩。
initWithContentsOfURL: 根據(jù)制定的文件路徑來初始化眶明。
registerItemForTypeIdentifier:loadHandler: 為一種資源類型自定義加載過程惩坑。這個(gè)方法主要針對自定義資源使用,例如自己定義的類或者文件格式等朝扼。當(dāng)調(diào)用loadItemForTypeIdentifier:options:completionHandler:方法時(shí)就會觸發(fā)定義的加載過程软吐。
hasItemConformingToTypeIdentifier: 用于判斷是否有typeIdentifier(UTI)所指定的資源存在。存在則返回YES吟税,否則返回NO。該方法結(jié)合loadItemForTypeIdentifier:options:completionHandler:使用姿现。
loadItemForTypeIdentifier:options:completionHandler: 加載typeIdentifier指定的資源肠仪。加載是一個(gè)異步過程,加載完成后會觸發(fā)completionHandler备典。
loadPreviewImageWithOptions:completionHandler: 加載資源的預(yù)覽圖片异旧。

NSItemProvider結(jié)構(gòu)說明

字段 說明
initWithItem:typeIdentifier: 初始化方法,item為附件的數(shù)據(jù)提佣,typeIdentifier是附件對應(yīng)的類型標(biāo)識,對應(yīng)UTI的描述吮蛹。
initWithContentsOfURL: 根據(jù)制定的文件路徑來初始化。
registerItemForTypeIdentifier:loadHandler: 為一種資源類型自定義加載過程拌屏。這個(gè)方法主要針對自定義資源使用潮针,例如自己定義的類或者文件格式等。當(dāng)調(diào)用loadItemForTypeIdentifier:options:completionHandler:方法時(shí)就會觸發(fā)定義的加載過程倚喂。
hasItemConformingToTypeIdentifier: 用于判斷是否有typeIdentifier(UTI)所指定的資源存在每篷。存在則返回YES,否則返回NO端圈。該方法結(jié)合loadItemForTypeIdentifier:options:completionHandler:使用焦读。
loadItemForTypeIdentifier:options:completionHandler: 加載typeIdentifier指定的資源。加載是一個(gè)異步過程舱权,加載完成后會觸發(fā)completionHandler矗晃。
loadPreviewImageWithOptions:completionHandler: 加載資源的預(yù)覽圖片。

由此可見宴倍,其結(jié)構(gòu)如下圖所示:

image

為了要取到宿主程序提供的數(shù)組张症,那么只要關(guān)注loadItemTypeIdentifier:options:completionHandler方法的使用即可。有了上面的了解啊楚,那么接下來就是對inputItems進(jìn)行數(shù)據(jù)分析并提取了吠冤,這里以一個(gè)鏈接的分享為例,改寫視圖控制器中的didSelectPost方法恭理≌蓿看下面的代碼:

- (void)didSelectPost
{
    __block BOOL hasExistsUrl = NO;
    [self.extensionContext.inputItems enumerateObjectsUsingBlock:^(NSExtensionItem * _Nonnull extItem, NSUInteger idx, BOOL * _Nonnull stop) {

        [item.attachments enumerateObjectsUsingBlock:^(NSItemProvider * _Nonnull itemProvider, NSUInteger idx, BOOL * _Nonnull stop) {

            if ([itemProvider hasItemConformingToTypeIdentifier:@"public.url"])
            {
                [itemProvider loadItemForTypeIdentifier:@"public.url"
                                                options:nil
                                      completionHandler:^(id<NSSecureCoding>  _Nullable item, NSError * _Null_unspecified error) {

                                          if ([(NSObject *)item isKindOfClass:[NSURL class]])
                                          {
                                              NSLog(@"分享的URL = %@", item);
                                          }

                                      }];

                hasExistsUrl = YES;
                *stop = YES;
            }

        }];

        if (hasExistsUrl)
        {
            *stop = YES;
        }

    }];

    // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
    // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
//    [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
}

上面的例子中遍歷了extensionContext的inputItems數(shù)組中所有NSExtensionItem對象,然后從這些對象中遍歷attachments數(shù)組中的所有NSItemProvider對象。匹配第一個(gè)包含public.url標(biāo)識的附件(具體要匹配什么資源涯保,數(shù)量是多少皆有自己的業(yè)務(wù)所決定)诉濒。注意:在上面代碼中注釋了[self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];這行代碼,主要是使到視圖控制器不被關(guān)閉夕春,等到實(shí)現(xiàn)相應(yīng)的處理后再進(jìn)行調(diào)用該方法未荒,對分享視圖進(jìn)行關(guān)閉。在下面的章節(jié)會說明這一點(diǎn)及志。

2.2.5 將分享數(shù)據(jù)傳遞給容器程序

上面章節(jié)已經(jīng)講述了如何取得宿主應(yīng)用所分享的內(nèi)容片排。那么,接下來就是將這些內(nèi)容傳遞給容器程序進(jìn)行相應(yīng)的操作(如:在一款社交應(yīng)用中速侈,可能會為取得的分享內(nèi)容發(fā)布一條用戶動態(tài))率寡。在默認(rèn)情況下,iOS的應(yīng)用是存在一個(gè)沙盒里面的倚搬,不允許應(yīng)用與應(yīng)用直接進(jìn)行數(shù)據(jù)的交互冶共。為此,蘋果提供了一項(xiàng)叫App Groups的服務(wù)每界,該服務(wù)允許開發(fā)者可以在自己的應(yīng)用之間通過NSUserDefaults捅僵、NSFileManager或者CoreData來進(jìn)行相互的數(shù)據(jù)傳輸。下面介紹如何激活A(yù)pp Groups服務(wù):

  • 首先要有一個(gè)獨(dú)立的AppID(帶通配符*號的AppID是不允許激活A(yù)pp Groups的)
image
  • 然后打開容器應(yīng)用的項(xiàng)目配置的Capabilities頁簽眨层,激活A(yù)pp Groups特性庙楚,如圖:
image
  • 點(diǎn)擊+號添加一個(gè)App Groups,點(diǎn)擊OK按鈕
image
  • 創(chuàng)建完成后谐岁,XCode會自動把應(yīng)用添加到新建的分組中醋奠。如圖:
image
  • 上述步驟完成后,容器程序的App Groups已經(jīng)算是設(shè)置完成伊佃。然后輪到Share Extension插件需要激活A(yù)pp Groups服務(wù)窜司,設(shè)置步驟跟容器程序相同,唯一不同的是航揉,插件不需要?jiǎng)?chuàng)建新的App Group塞祈,只要加入到容器程序剛才創(chuàng)建的Group即可(這里可以理解為,哪些應(yīng)用要實(shí)現(xiàn)共享數(shù)據(jù)帅涂,那么他們必須在同一個(gè)Group里面)议薪。如圖:
image

至此,應(yīng)用和擴(kuò)展的App Groups服務(wù)都已經(jīng)啟動媳友,現(xiàn)在就要進(jìn)行分享內(nèi)容的傳輸操作斯议。下面分別介紹一下NSUserDefaults、NSFileManager以及CoreData三種方式是如何實(shí)現(xiàn)App Groups下的數(shù)據(jù)操作:

  • NSUserDefaults:要想設(shè)置或訪問Group的數(shù)據(jù)醇锚,不能在使用standardUserDefaults方法來獲取一個(gè)NSUserDefaults對象了哼御。應(yīng)該使用initWithSuiteName:方法來初始化一個(gè)NSUserDefaults對象坯临,其中的SuiteName就是創(chuàng)建的Group的名字,然后利用這個(gè)對象來實(shí)現(xiàn)恋昼,跨應(yīng)用的數(shù)據(jù)讀寫看靠,代碼如下:
//初始化一個(gè)供App Groups使用的NSUserDefaults對象
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.cn.vimfung.ShareExtensionDemo"];

//寫入數(shù)據(jù)
[userDefaults setValue:@"value" forKey:@"key"];

//讀取數(shù)據(jù)
NSLog(@"%@", [userDefaults valueForKey:@"key"]);
  • NSFileManager:通過調(diào)用 containerURLForSecurityApplicationGroupIdentifier:方法可以獲得AppGroup的共享目錄,然后在此目錄的基礎(chǔ)上實(shí)現(xiàn)任意的文件操作液肌。代碼如下:
//獲取分組的共享目錄
NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.cn.vimfung.ShareExtensionDemo"];
NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"demo.txt"];

//寫入文件
[@"abc" writeToURL:fileURL atomically:YES encoding:NSUTF8StringEncoding error:nil];

//讀取文件
NSString *str = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:nil];
NSLog(@"str = %@", str);
  • CoreData:其實(shí)CoreData是基于NSFileManager取得共享目錄后來實(shí)現(xiàn)數(shù)據(jù)共享的挟炬。即在初始化CoreData時(shí),先使用NSFileManager取得共享目錄嗦哆,然后再指定共享目錄為存儲數(shù)據(jù)文件的目錄(如存儲的sqlite文件)谤祖。代碼如下:
//獲取分組的共享項(xiàng)目
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.cn.vimfung.ShareExtensionDemo"];
NSURL *storeURL = [containerURL URLByAppendingPathComponent:@"DataModel.sqlite"];

//初始化持久化存儲調(diào)度器
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"DataModel" withExtension:@"momd"];

NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];

[coordinator addPersistentStoreWithType:NSSQLiteStoreType
                          configuration:nil
                                    URL:storeURL
                                options:nil
                                  error:nil];

//創(chuàng)建受控對象上下文
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

[context performBlockAndWait:^{
    [context setPersistentStoreCoordinator:coordinator];
}];

為了方便演示,這里會使用NSUserDefault來直接把取到的url地址保存起來老速。代碼如下所示:

/**
 *  點(diǎn)擊提交按鈕
 */
- (void)didSelectPost
{
    __block BOOL hasExistsUrl = NO;
    [self.extensionContext.inputItems enumerateObjectsUsingBlock:^(NSExtensionItem * _Nonnull extItem, NSUInteger idx, BOOL * _Nonnull stop) {

        [item.attachments enumerateObjectsUsingBlock:^(NSItemProvider * _Nonnull itemProvider, NSUInteger idx, BOOL * _Nonnull stop) {

            if ([itemProvider hasItemConformingToTypeIdentifier:@"public.url"])
            {
                [itemProvider loadItemForTypeIdentifier:@"public.url"
                                                options:nil
                                      completionHandler:^(id<NSSecureCoding>  _Nullable item, NSError * _Null_unspecified error) {

                                          if ([(NSObject *)item isKindOfClass:[NSURL class]])
                                          {
                                              NSLog(@"分享的URL = %@", item);
                                              NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.cn.vimfung.ShareExtensionDemo"];
                                              [userDefaults setValue: ((NSURL *)item).absoluteString forKey:@"share-url"];
                                               //用于標(biāo)記是新的分享
                                              [userDefaults setBool:YES forKey:@"has-new-share"];
                                          }

                                      }];

                hasExistsUrl = YES;
                *stop = YES;
            }

        }];

        if (hasExistsUrl)
        {
            *stop = YES;
        }

    }];

    // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
    // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
//    [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
}
2.2.6 自定義UI

如果通過擴(kuò)展SLComposeServiceViewController還不能滿足需求的情況下泊脐,這時(shí)候就需要自己設(shè)計(jì)一個(gè)分享視圖控制器來替換默認(rèn)的SLComposeServiceViewController。

  1. 首先烁峭,創(chuàng)建一個(gè)自定義視圖控制器,如:CustomShareViewController秕铛。

  2. 然后打開擴(kuò)展的Info.plist文件约郁,刪除NSExtensionMainStoryboard屬性并增加一項(xiàng)NSExtensionPrincipalClass屬性并指向CustomShareViewController(注:這里沒有使用Storyboard所以要?jiǎng)h除該屬性),如圖:


    image.png
  3. 接下來根據(jù)實(shí)際的需要來設(shè)計(jì)分享視圖的展示與交互形式但两。

  4. 然后調(diào)用CustomShareViewController的extensionContext屬性來控制擴(kuò)展的提交與取消等操作(注:由于擴(kuò)展中導(dǎo)入了關(guān)于ExtensionContext的UIViewController類目鬓梅,因此,每個(gè)ViewController都帶有extensionContext屬性)谨湘。

2.2.7 演示的效果圖

對于html分享和image分享的UI分開采用不同的形式展示


image.png

image.png

注意事項(xiàng)

3.1 提審AppStore的注意事項(xiàng)

擴(kuò)展中的處理不能太長時(shí)間阻塞主線程(建議放入線程中處處理)绽快,否則可能導(dǎo)致蘋果拒絕你的應(yīng)用。
擴(kuò)展不能單獨(dú)提審紧阔,必須要跟容器程序一起提交AppStore進(jìn)行審核坊罢。
提審的擴(kuò)展和容器程序的Build Version要保持一致,否則在上傳審核包的時(shí)候會提示警告擅耽,導(dǎo)致程序無法正常提審活孩。

附上源碼的git地址

源碼git地址

參考文獻(xiàn)

Inter-App Communication
App Extensions篇之Share Extension
iOS實(shí)現(xiàn)App之間的內(nèi)容分享
iOS擴(kuò)展開發(fā)攻略(一) - Share Extension
App Extensions篇之Share Extension

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市乖仇,隨后出現(xiàn)的幾起案子憾儒,更是在濱河造成了極大的恐慌,老刑警劉巖乃沙,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件起趾,死亡現(xiàn)場離奇詭異,居然都是意外死亡警儒,警方通過查閱死者的電腦和手機(jī)训裆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缭保,你說我怎么就攤上這事汛闸。” “怎么了艺骂?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵诸老,是天一觀的道長。 經(jīng)常有香客問我钳恕,道長别伏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任忧额,我火速辦了婚禮厘肮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘睦番。我一直安慰自己类茂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布托嚣。 她就那樣靜靜地躺著巩检,像睡著了一般。 火紅的嫁衣襯著肌膚如雪示启。 梳的紋絲不亂的頭發(fā)上兢哭,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音夫嗓,去河邊找鬼迟螺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛舍咖,可吹牛的內(nèi)容都是我干的矩父。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼排霉,長吁一口氣:“原來是場噩夢啊……” “哼浙垫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起郑诺,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤夹姥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后辙诞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辙售,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年飞涂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了旦部。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祈搜。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖士八,靈堂內(nèi)的尸體忽然破棺而出容燕,到底是詐尸還是另有隱情,我是刑警寧澤婚度,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布蘸秘,位于F島的核電站,受9級特大地震影響蝗茁,放射性物質(zhì)發(fā)生泄漏醋虏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一哮翘、第九天 我趴在偏房一處隱蔽的房頂上張望颈嚼。 院中可真熱鬧,春花似錦饭寺、人聲如沸阻课。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柑肴。三九已至,卻和暖如春旬薯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背适秩。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工绊序, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秽荞。 一個(gè)月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓骤公,卻偏偏與公主長得像,于是被迫代替她去往敵國和親扬跋。 傳聞我的和親對象是個(gè)殘疾皇子阶捆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容