iOS8.0加入了擴展,iOS10蘋果又增加了很多擴展昌屉。在今后钙蒙,程序中會集成越來越多的擴展功能。
今天主要來模仿1password實現(xiàn)在其他APP登錄時自動填充賬號间驮、密碼躬厌。通過這一功能開發(fā)了解擴展。
這是一個很有意思的功能竞帽。
我們先來看功能實現(xiàn)效果扛施。
1passwrod是一款密碼管理類app,我們可以在登錄時喚醒1password并獲取到相應的賬號密碼屹篓,然后填充到輸入框中以實現(xiàn)賬號密碼的自動填充疙渣、登錄。
這種app之間的互相訪問堆巧、數(shù)據(jù)共享看起來與我們以往的開發(fā)經(jīng)歷所不同妄荔。這種功能實現(xiàn),不僅使APP更加靈活恳邀,還提升用戶體驗懦冰。
如何實現(xiàn)的呢,看完這篇文章谣沸,你也能學會刷钢。
先來創(chuàng)建一個demo工程,工程名為ExtensionDemo乳附。
網(wǎng)上的文檔有很多内地,但基本都以一個簡單的demo為主伴澄,在我創(chuàng)建的demo中,涉及到了宿主應用和應用擴展的數(shù)據(jù)庫共享阱缓、類共享非凌、xib共享、以及宿主應用和應用擴展荆针、應用擴展和host app的相互通信敞嗡。把需求實現(xiàn)過程中遇到的坑全部描述清晰,幫助小伙伴少走彎路航背。
開始之前喉悴,我們需要了解一些理論知識。
host app:通過點擊系統(tǒng)分享菜單中的插件圖標調(diào)起擴展程序玖媚,在gif圖片中箕肃,喚起1password的應用就是host app。
宿主應用:也叫Containing App今魔,簡單點說勺像,我們創(chuàng)建一個Xcode工程,然后運行項目错森,這個就是宿主應用吟宦。
應用擴展:也叫App Extension,打包運行在手機上時涩维,會隨著宿主應用一起安裝在手機上督函。詳細來說,gif圖中喚起的應用并不是1password本身激挪,而是1password的應用擴展辰狡。是獨立于宿主應用之外的。
總結(jié)一下:應用擴展就是宿主應用和host app溝通的橋梁垄分,使宿主應用和Host App的數(shù)據(jù)共享成為可能宛篇。
他們的關系圖如下:
宿主應用 & 應用擴展
好,開始薄湿。
宿主應用和應用應用是在一個工程下叫倍,用戶安裝APP后,如果工程內(nèi)有應用擴展豺瘤,應用擴展也會默認安裝在用戶的手機上吆倦。
先來看一下宿主應用的顯示效果。
工程文件
這里為了方便坐求,使用PasswordDBTool來操作數(shù)據(jù)庫蚕泽,沒有使用Key-Value式的存儲,不過這里不是本次的重點。數(shù)據(jù)庫相關下次來寫须妻。
好仔蝌,到這里,宿主應用所需要的東西我們都搞定了荒吏。接下來敛惊,開始應用擴展的開發(fā)和相應的配置。
在之前绰更,我們已經(jīng)了解到瞧挤,應用擴展屬于應用的擴展。擴展是iOS8.0加入的一個非常強大的功能儡湾。接下來開始在項目中加入擴展皿伺。
1、添加擴展Target
2盒粮、操作完的工程文件
這些都是添加完擴展target后系統(tǒng)默認為工程生成的。
當然奠滑,ActionViewController的.h.m文件和MainInterface.storyboard文件我們都可以隨便的對其更改丹皱。其實這三個文件和我們平時創(chuàng)建使用的類文件和storyboard文件并無兩樣。同樣支持拖線等操作宋税。
3摊崭、接下來我們看一下ActionViewController的.h和.m文件中的代碼內(nèi)容。
系統(tǒng)創(chuàng)建的ActionViewController默認繼承自UIViewController杰赛,當然我們也可以對這里進行更改呢簸,讓其繼承自UITableViewController以便之后的開發(fā)。
重點來講一下圖2中的代碼內(nèi)容乏屯。
(1)self.extensionContext
command+鼠標左鍵點進去看看根时,發(fā)現(xiàn)是這樣的。
發(fā)現(xiàn)self.extensionContext是NSExtensionContext對象辰晕。見名知意蛤迎,extensionContext即擴展上下文,用來聯(lián)系宿主應用和應用擴展含友,它們倆之間的通信就是靠extensionContext替裆。
(2)NSExtensionItem
待處理的數(shù)據(jù),宿主應用和應用擴展之間通信的數(shù)據(jù)(參數(shù)等)我們可以放到NSExtensionItem對象中窘问。在各自的應用中通過NSExtensionItem獲取通信數(shù)據(jù)辆童。
(3)NSItemProvider
確切來說,宿主應用和應用擴展之間需要傳遞的數(shù)據(jù)是放在NSItemProvider對象中的惠赫。
那么把鉴,NSItemProvider對象是如何進行數(shù)據(jù)存儲的?重點在這里儿咱。
通過NSItemProvider對象的
loadItemForTypeIdentifier:options:completionHandler:方法纸镊。
這里有一個特別需要注意的點倍阐,就是第一個參數(shù)的傳值。command+鼠標左鍵點擊第一個參數(shù)KUTypeImage逗威,進去會發(fā)現(xiàn)有幾十個這樣的參數(shù)峰搪。當然,每一種參數(shù)的含義都不相同凯旭,這里不一一詳解概耻。如果這里的參數(shù)值傳的是KUTypeImage則相應的,宿主應用傳遞過來的數(shù)據(jù)是一個圖片罐呼。如果這里的參數(shù)值傳的是kUTTypePropertyList鞠柄,相應的,宿主應用傳遞過來的數(shù)據(jù)可能是一個字典嫉柴。
但是在我們的demo中厌杜,我們不使用系統(tǒng)提供的這些參數(shù),而使用自定義參數(shù)计螺。格式如下:
具體是什么含義會在下面陸續(xù)講解夯尽。因為這里需要host app協(xié)同操作才能看的更明白。
應用擴展
我們都知道登馒,iOS應用具有沙盒機制匙握。app之間是不能進行數(shù)據(jù)共享的。而在文章開頭展示的gif圖卻給我們造成一種假象陈轿,即我們在app中可以去訪問其他app的數(shù)據(jù)圈纺,有種“app之間可以進行數(shù)據(jù)共享”的錯覺。而這種錯覺就是應用“擴展”給我們造成的麦射,擴展使app之間的數(shù)據(jù)共享成為了一種可能蛾娶。使app變得更加靈活。
現(xiàn)在潜秋,我們要實現(xiàn)的需求是這樣的:在host app中喚起應用的擴展茫叭,host app需要傳給應用擴展一個URL參數(shù),應用擴展根據(jù)host app傳遞過來的URL參數(shù)在宿主應用內(nèi)的數(shù)據(jù)庫中查找符合條件的數(shù)據(jù)半等,再把符合條件的數(shù)據(jù)回傳給host app揍愁。
整個流程是這樣的。
在整個通信過程中杀饵,難點在于宿主應用和應用擴展的數(shù)據(jù)共享莽囤,不僅僅是數(shù)據(jù)共享,可能還需要共享一些開發(fā)文件切距,比如類文件朽缎、xib、storyboard等。不要以為宿主應用和應用擴展同屬于一個工程項目话肖,它們兩個就可以共同使用項目內(nèi)的數(shù)據(jù)和所有文件北秽。這是錯誤的。那么最筒,宿主應用和應用擴展如何進行數(shù)據(jù)共享贺氓?我們需要創(chuàng)建一個共享域,當然床蜘,蘋果早就給我們準備好了辙培,我們只需要配置一下即可。
1邢锯、配置共享域
(1)配置宿主應用共享域
點擊ON后扬蕊,其實App Groups這里是空的,因為我之前做項目有配置過共享域丹擎,所以在選擇證書的時候尾抑,系統(tǒng)會把證書配置過的共享域都給我自動加載了出來。如果這里是空的蒂培,就點擊下面的+號再愈,添加一個共享域。
這時毁渗,Xcode會彈出提示框,讓你給共享庫起一個名字以辨別单刁,因為有些項目可能需要不只一個共享域灸异,如果項目支持Apple watch,就需要一個新的共享域支持Apple watch羔飞。共享域的名字以group.開頭肺樟,名字自己起。
OK逻淌,添加完共享域后么伯,新的共享域就出現(xiàn)在了APP Groups中,選中它卡儒。
到這里田柔,宿主應用的共享域配置告一段落。
(2)配置應用擴展
點擊ON后骨望,系統(tǒng)會彈出提示框硬爆,讓你選擇證書,因為共享域是在證書的基礎上配置的擎鸠。證書選擇后缀磕,會把對應的所有共享域顯示在App Groups中。
選中我們之前在宿主應用創(chuàng)建(選擇)的共享域。
OK袜蚕,應用擴展的共享域配置完畢糟把。
2、數(shù)據(jù)共享
(1)NSUserDefaults
NSUserDefaults *userDefault = [[NSUserDefaults alloc] initWithSuiteName:@"group.testAppExtension"];
獲取共享域的偏好設置
接下來平時怎么用這里就怎么用牲剃。
(2)數(shù)據(jù)庫
在創(chuàng)建應用擴展前遣疯,數(shù)據(jù)庫我是放到這個路徑下的。
[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"TestDB.sqlite"]
而現(xiàn)在颠黎,即使共享域配置完畢另锋,應用擴展繼續(xù)訪問這個路徑下的數(shù)據(jù)庫也是訪問不到的,因為共享域它有自己的路徑。宿主應用和應用擴展之間的空間關系如下:
所以耸携,我們要將數(shù)據(jù)庫放在共享域的路徑下雏婶。共享域的路徑如下:
[[[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.testAppExtension"] absoluteString] stringByAppendingPathComponent:@"TestDB.sqlite"]
通過containerURLForSecurityApplicationGroupIdentifier方法和共享域標識符我們可以獲取到該共享域的路徑
OK,共享數(shù)據(jù)到這里暫告一段落室梅。
3、應用擴展開始編碼
前面說了這里可以隨便改疚宇,修改后的結(jié)構(gòu)如下:
這里別忘了把新的storyboard和控制器關聯(lián)一下亡鼠。
然后我們來看ActionViewController.m文件。
target選擇PassowrdAppExtension進行調(diào)試敷待。
然后選擇在哪個host app中進行測試间涵。
點擊RUN,報錯榜揖。
通過錯誤信息可以知道是文件引用錯誤勾哩。
這是因為此時應用擴展還不能隨便使用項目內(nèi)的其他文件。因為到目前為止举哟,都是宿主應用的target在引用這些文件思劳。
看到這個錯誤我的第一反應是把Password和PasswordDBTool的類文件加入到應用擴展 target 的編譯文件中去,這樣在擴展中自然也就可以使用了妨猩。但是潜叛,文件數(shù)量少,這樣做還可以壶硅。如果文件數(shù)量大威兜,再這樣做會十分麻煩,出錯的概率會大大增加庐椒,效率也十分低下牡属,所有類弄的團團糟維護起來也很麻煩。所幸我們可以創(chuàng)建一個Framework文件扼睬,讓Framework文件引用這些需要共享的類逮栅,再讓宿主應用和應用擴展分別導入Framework文件悴势。這樣做就很好的解決了問題,還不容易出錯措伐,也便于后期維護特纤。
一步一步來實現(xiàn)剛才說的。
1侥加、創(chuàng)建framework文件
framework文件的命名規(guī)范一些捧存,以Kit為結(jié)尾。
創(chuàng)建完framework后工程目錄如下
2担败、引用文件
(1)先把宿主應用target的文件引用刪除昔穴,因為應用擴展同樣要使用FMDB,所以也要把第三方文件從target中刪除提前,否則編譯照樣會報錯吗货。
點擊Compile Sources下面的-號把標注的類全部刪除。
最后只剩下3個文件狈网。
(2)增加AppExtensionKit的引用文件
點+號把剛才刪除的類加進來宙搬。添加完后如下:
需要注意的是,在這里不要添加xib文件拓哺,xib在哪修改下面會說勇垛。
(3)為應用擴展導入AppExtensionKit文件
添加完后編譯一下,報錯士鸥,40多個闲孤。
這是因為應用擴展也要用到libsqlite3.0.tbd這個包,但是并沒有為應用擴展添加這個包烤礁,所以讼积,重復上面的操作,把libsqlite3.0.tbd加入到AppExtensionKit中鸽凶。
再編譯一下币砂,錯誤全部消失不見建峭。OK玻侥,配置全部完成。
(4)豐富一下ActionViewController.m的代碼亿蒸,把共享區(qū)數(shù)據(jù)庫的數(shù)據(jù)全部打印出來凑兰。
編譯無錯,運行崩潰边锁。崩潰位置是第40行姑食。
原因:PasswrodCell是從xib加載的,但我們并沒有把xib文件加入到AppExtensionKit中茅坛。知道問題出在哪了音半,去解決则拷。
在宿主應用的target中,找到PasswordCell的引用并刪除曹鸠。如下:
在targets中選中AppExtesnionKit煌茬,為其添加Password.xib的引用,如下:
操作完后彻桃,xib文件從原來bundle下的路徑變成了bundle下AppExtensionKit下的路徑坛善。
做完這些還不夠,我們還要在ExtensionDemo和PasswordAppExtension兩個target下的Copy Bundle Resources中將AppExtensionKit導入進來邻眷,否則宿主應用和應用擴展還是用不了PasswordCell.xib眠屎。如圖:
ExtensionDemo的target:
PasswordAppExtension的target:
那我們再次加載Password.xib文件,就需要從Bundle下的AppExtensionKit文件中加載肆饶。
加載方式代碼如下:
cell = [[NSBundle mainBundle] loadNibNamed:@"AppExtensionKit.framework/ExtensionCell" owner:nil options:nil].lastObject;
運行項目改衩,效果如圖:
和宿主應用顯示的數(shù)據(jù)一模一樣。
自此抖拴,宿主應用與應用擴展的數(shù)據(jù)共享就完成了燎字。
接下來,是Host App和應用擴展之間的數(shù)據(jù)傳遞阿宅。
Host App
Host App界面實現(xiàn)和代碼邏輯都比較簡單候衍。
實現(xiàn)效果如下:
代碼部分:
點擊按鈕時會觸發(fā)如下代碼:
這里有幾個關鍵點:
(1)首先,我創(chuàng)建了一個字典并且保存了兩個參數(shù)洒放,一個是版本號蛉鹿,一個是URLKey(我要將這個參數(shù)傳遞給應用擴展,應用擴展會用這個key做為查詢條件到數(shù)據(jù)庫中查詢數(shù)據(jù)往湿,然后將查詢到的數(shù)據(jù)再回傳給host app)妖异。
(2)我把這個字典賦值給了NSItemProvider的item屬性,又將NSItemProvider對象添加到了NSExtensionItem對象的attachments數(shù)組中领追。在應用擴展中他膳,我們也按照這種方式來逐步獲取字典。
(3)前面說過绒窑,系統(tǒng)提供了KUTTypeImage等字段用來在應用擴展中獲取來自host app傳遞過來的值棕孙,而這個字段我們是可以自定義的。如圖些膨,這個自定義字段也是通過NSItemProvider對象來傳遞的蟀俊。
(3)在應用擴展中,我們?nèi)绾瓮ㄟ^這個自定義字段來獲取host app傳遞過來的數(shù)據(jù)订雾。如圖:
關鍵代碼已經(jīng)用紅色方框標注出來了肢预。
也就是說通過這句代碼我們可以獲取到host app向應用傳遞的typeIdentifier。這兩個地方要一致才能獲取到host app傳遞過來的數(shù)據(jù)洼哎。
在block回調(diào)中把host app傳遞過來的數(shù)據(jù)取出來烫映,然后到數(shù)據(jù)庫中進行查詢就可以了沼本。
(4)數(shù)據(jù)查詢到了怎么回傳給host app呢?
剛才已經(jīng)展示過了應用擴展的界面锭沟,應用擴展實現(xiàn)了與宿主應用的數(shù)據(jù)共享擅威。如圖:
當點擊右上角關閉按鈕時,什么數(shù)據(jù)都不回傳冈钦。
當點擊某個cell時郊丛,把對應的數(shù)據(jù)(也就是某條密碼)回傳給Host App,并把該密碼的賬戶和密碼顯示在對應的輸入框中瞧筛。
代碼如下:
關閉按鈕的點擊事件:
單元格點擊事件:
到這里厉熟,應用擴展對host app的數(shù)據(jù)回傳就搞定了。
(5)host app拿到回傳數(shù)據(jù)進行登錄
這一步是通過UIActivityViewController對象的回調(diào)完成的较幌。
不管是把數(shù)據(jù)從host app傳給應用擴展揍瑟,
還是把數(shù)據(jù)從應用擴展傳給host app,
數(shù)據(jù)的傳遞依靠的都是NSExtensionItem和NSItemProvider乍炉,
如果非要給他們弄一個關系便于理解的話绢片,大概是這樣的:
存:
需要傳遞的數(shù)據(jù) -> NSItemProvider -> NSExtensionItem -> NSExtensionContext
取:
NSExtensionContext -> NSExtensionItem -> NSItemProvider -> 拿到需要傳遞的數(shù)據(jù)
一層一層的包裹著岛琼。
OK底循,全部搞定。
我們來看一下最終的效果槐瑞。
額熙涤,還差一點。
沒有給我們的應用擴展配置一個圖標困檩。
OK祠挫,全部搞定。
需求實現(xiàn)了悼沿。
但是在使用擴展的過程中還是有不少的坑等舔,為了謹慎起見,在擴展中編寫代碼調(diào)用方法糟趾,多看看文檔慌植。有很多方法都有官方注釋,有些方法是不能在應用擴展中使用的拉讯。
好涤浇,今天就到這里鳖藕。