iOS Today Extension開(kāi)發(fā)心得

前段時(shí)間因?yàn)轫?xiàng)目需要,接觸了iOS Today Extension 的開(kāi)發(fā),網(wǎng)絡(luò)上關(guān)于完整的 Today Extension開(kāi)發(fā)的資料也不多,所以期間遇到了不少的坑,現(xiàn)在把新心得寫(xiě)出來(lái),希望會(huì)對(duì)讀者有點(diǎn)用處。

先來(lái)科普一下:

擴(kuò)展(Extension)是iOS 8加入的一個(gè)強(qiáng)大功能,可以通過(guò)系統(tǒng)給我們的擴(kuò)展接入點(diǎn),來(lái)為系統(tǒng)的服務(wù)提供某些附加的功能,擴(kuò)展的接入點(diǎn)有以下幾個(gè):

今日(Today)- 在下拉通知的“今天”的界面中添加一個(gè)小插件

分享(Share)- 點(diǎn)擊分享按鈕后,將網(wǎng)站或者圖片通過(guò)應(yīng)用分享

操作(Action)- 點(diǎn)擊Action按鈕后發(fā)送內(nèi)容到應(yīng)用

圖片編輯(Photo Editing)- 在系統(tǒng)的照片應(yīng)用中提供編輯的功能

文檔管理(Document Provider)- 提供和管理文件內(nèi)容

自定義鍵盤(pán)(Custom keyboard)- 自定義鍵盤(pán)和輸入法

iOS 9 新增了4個(gè):

音頻單元(Audio Unit)- 為音樂(lè)App提供擴(kuò)展功能,例如GarageBand

Spotlight索引(Spotlight Index)- Spotlight搜索擴(kuò)展

共享的鏈接(Shared Links)- Safair共享的連接擴(kuò)展

廣告攔截(Content Blocker)- Safair廣告攔截?cái)U(kuò)展


需要注意的幾件事:

1、擴(kuò)展在 iOS 中是不能以單獨(dú)的形式存在,是隨著容器 App(Container App)一起打包提供的。

2、擴(kuò)展的生命周期和容器 App(Container App)本身的生命周期是獨(dú)立的,它們是兩個(gè)獨(dú)立的進(jìn)程,默認(rèn)情況下互相不應(yīng)該知道對(duì)方的存在。也就是說(shuō),擴(kuò)展本身就是一個(gè)小型的App,App id也與容器App不一樣。

3、提供擴(kuò)展的方式是在 App 的項(xiàng)目中加入相應(yīng)的擴(kuò)展的 Target

4、擴(kuò)展應(yīng)該保持輕巧迅速,并且專(zhuān)注功能單一,在不打擾或者中斷用戶(hù)使用當(dāng)前應(yīng)用的前提下完成自己的功能點(diǎn)。

擴(kuò)展和應(yīng)用的交互:

上面提到了,擴(kuò)展和容器應(yīng)用是兩個(gè)獨(dú)立的App,但是擴(kuò)展可以共享容器應(yīng)用本身的邏輯、界面和數(shù)據(jù)

1、使用iOS 8 新引入的自制 framework 的方式來(lái)組織需要重用的代碼,這樣在鏈接 framework 后容器App 和擴(kuò)展就都能使用相同的代碼了。

2、通過(guò)開(kāi)啟 App Groups 和進(jìn)行相應(yīng)的配置來(lái)開(kāi)啟在兩個(gè)進(jìn)程間的數(shù)據(jù)共享。這包括了使用NSUserDefaults進(jìn)行小數(shù)據(jù)的共享,或者使用NSFileCoordinator和NSFilePresenter甚至是 CoreData 和 SQLite 來(lái)進(jìn)行更大的文件或者是更復(fù)雜的數(shù)據(jù)交互。

3、可通過(guò)自定義的 url scheme ,從擴(kuò)展向應(yīng)用反饋數(shù)據(jù)和交互。

廢話(huà)不多說(shuō)了,下面進(jìn)入項(xiàng)目實(shí)例的環(huán)節(jié)。

首先打開(kāi)項(xiàng)目,點(diǎn)擊Xcode菜單的File->New->Target,然后選擇 iOS 中的 Application Extension 的 Today Extension

選擇需要添加的擴(kuò)展

在彈出的菜單中將新的 Target 命名為xxxWidget,并且讓 Xcode 自動(dòng)生成新的 Scheme,以方便測(cè)試使用。我們的工程中現(xiàn)在會(huì)多出一個(gè)和新建的 Target 同名的文件夾,里面主要包含了TodayViewController.h和TodayViewController.m 的 ViewController 程序文件,一個(gè)叫做MainInterface的 storyboard 文件和 Info.plist。其中在 plist 里 的NSExtension中定義了這個(gè) 擴(kuò)展的類(lèi)型和入口,而配套的 ViewController 和 StoryBoard 就是我們的擴(kuò)展的具體內(nèi)容和實(shí)現(xiàn)了。

首先把info.plist的Bundle display name 改為自己產(chǎn)品的名字

info.plist

運(yùn)行程序,然后下拉通知欄,點(diǎn)擊今天,點(diǎn)擊編輯,就看到可以添加剛剛自己創(chuàng)建的擴(kuò)展了,擴(kuò)展顯示的名字可以在剛剛的info.plist中修改。

添加擴(kuò)展

調(diào)試擴(kuò)展的方式有兩種:

1、選擇這個(gè)擴(kuò)展的scheme直接運(yùn)行

2、先運(yùn)行容器App,然后下拉通知欄打開(kāi)今日擴(kuò)展,點(diǎn)擊Xcode菜單的Debug->Attach To Process ,選擇Likly Targets中需要調(diào)試的擴(kuò)展

新創(chuàng)建的Today擴(kuò)展,只包括一個(gè)Hello World的Label,根據(jù)UI設(shè)計(jì),我需要在上面添加一個(gè)音量調(diào)節(jié)的Slider。啥都憋說(shuō),先拖個(gè)去storyboard,運(yùn)行爽一下。嗯,看上去好像挺完美(此處無(wú)圖),忍不住拖動(dòng)一下,先往右拖~~~手感很順滑,再往左拖~~~。。。。(╯‵□′)╯︵┻━┻!什么鬼!滑到通知界面去了!好吧,查了下資料,原來(lái)官方建議不要在這個(gè)界面上添加可以滑動(dòng)的插件,不然會(huì)導(dǎo)致用戶(hù)誤操作。好吧,看來(lái)只能修改UI了

修改后的UI

看修改后的UI,需要實(shí)現(xiàn)的功能就是,點(diǎn)擊“音量減”、“音量加”按鈕的時(shí)候,容器App的音量要作出相應(yīng)的改變。

沒(méi)關(guān)西(? ??_??)?,一步步來(lái),首先來(lái)實(shí)現(xiàn)音量調(diào)整的數(shù)據(jù)同步問(wèn)題:

在應(yīng)用和擴(kuò)展間共享數(shù)據(jù) - App Groups

對(duì) iOS 開(kāi)發(fā)者來(lái)說(shuō),沙盒限制了我們?cè)谠O(shè)備上隨意讀取和寫(xiě)入。但是對(duì)于應(yīng)用和其對(duì)應(yīng)的擴(kuò)展來(lái)說(shuō),Apple 在 iOS 8 +中為我們提供了一種可能性,那就是 App Groups。App Groups 為同一個(gè) 開(kāi)發(fā)商 的應(yīng)用或者擴(kuò)展定義了一組域,在這個(gè)域中同一個(gè) group 可以共享一些資源。

首先我們需要開(kāi)啟 App Groups。得益于 Xcode 5 開(kāi)始引入的 Capabilities,這變得非常簡(jiǎn)單(至少不再需要去 developer portal 了)。選擇主 Target,打開(kāi)它的 Capabilities 選項(xiàng)卡,找到 App Groups 并打開(kāi)開(kāi)關(guān),然后添加一個(gè)你能記得的 group 名字,比如group.myWidget。接下來(lái)你還需要為xxxWidget這個(gè) Target 進(jìn)行同樣的配置,只不過(guò)不再需要新建 group,而是勾選剛才創(chuàng)建的 group 就行。

開(kāi)啟 App Groups

然后,使用以下代碼,就可以讀寫(xiě)共享的數(shù)據(jù)了

- (NSUserDefaults*)loadGroupData

{

NSUserDefaults* userDefault = [[NSUserDefaults alloc] initWithSuiteName:@"group.myWidget"];// SuiteName必須和上面Capabilities配置填寫(xiě)的一致

return userDefault;

}

- (void)setVolume:(uint32_t)volume

{

[[self loadGroupData] setObject:@(volume) forKey:@"volume"];

[[self loadGroupData] synchronize];

}

- (uint32_t)getVolume:(uint32_t)volume

{

NSNumber* volumeNumber = [[self loadGroupData] objectForKey:@"volume"];

if(volumeNumber && [volumeNumber isKindOfClass:[NSNumber class]]){

return volumeNumber.unsignedIntValue;

}else{

return kDefaultVolume;

}

}

擴(kuò)展或容器App在修改和獲取音量的時(shí)候,調(diào)用這些方法就OK了。

(P.S.可以通過(guò)自制 framework 的方式來(lái)組織需要重用的代碼,具體方法這里不多說(shuō)了)

在 TodayViewController.m 的 viewWillAppear: 方法中編寫(xiě)讀取共享數(shù)據(jù)并刷新界面的代碼,每次下拉,音量值Label就可以正確顯示了(沒(méi)錯(cuò),此處沒(méi)有代碼)。

接著。。。棘手的問(wèn)題來(lái)了!怎么去實(shí)現(xiàn)擴(kuò)展和容器App交互呢?使用 NSNotificationCenter,KVO,Delegate都無(wú)果,因?yàn)樗鼈儌z根本就不是同一個(gè)App!再試了下url scheme的方式,一點(diǎn)擊按鈕就跳到容器App去了,蛋疼!看來(lái)只能從共享數(shù)據(jù)上去做文章了,然后共享數(shù)據(jù)只能是主動(dòng)獲取數(shù)據(jù),所以初步想了如下兩個(gè)方案

1、容器App設(shè)置定時(shí)器去輪詢(xún)共享數(shù)據(jù),一旦發(fā)生變化,就相應(yīng)地改變音量。

2、擴(kuò)展發(fā)送請(qǐng)求到服務(wù)器,服務(wù)器再通知容器App。

But!我覺(jué)得這兩個(gè)方案都好low好傻逼好費(fèi)資源?。。?!為什么會(huì)醬紫!難道真的沒(méi)有方法可以實(shí)現(xiàn)了么!?。吭贐aidu,Google,Stack OverFlow上各種搜也找不到好的解決方法!

就在我快要著手第一個(gè)方案的時(shí)候,突然無(wú)意中百度到了一個(gè)大救星(真的是Baidu,不是Google),沒(méi)錯(cuò),它就是 CFNotificationCenterGetDarwinNotifyCenter!這是CoreFoundation庫(kù)中一個(gè)系統(tǒng)級(jí)的通知中心,蘋(píng)果的系統(tǒng)自己也在用它,看清了“Darwin””了沒(méi)有?哈哈!看了下CFNotificationCenter相關(guān)的API,跟NSNotificationCenter有點(diǎn)像。需要用到Toll-Bridge的知識(shí)與CoreFoundation相關(guān)的類(lèi)進(jìn)行橋接,這雖不常用但也不難。還需要注意下個(gè)別參數(shù)的使用。

// 添加監(jiān)聽(tīng)

- (void)addObserver

{

CFNotificationCenterRef notification = CFNotificationCenterGetDarwinNotifyCenter ();

CFNotificationCenterAddObserver(notification, (__bridge const void *)(self), observerMethod, CFSTR(“通知名”), NULL, CFNotificationSuspensionBehaviorDeliverImmediately);

}

void observerMethod (CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)

{

//監(jiān)聽(tīng)到notification后要做的處理

}

// 移除監(jiān)聽(tīng)

- (void)removeObserver

{

CFNotificationCenterRef notification = CFNotificationCenterGetDarwinNotifyCenter ();

CFNotificationCenterRemoveObserver(notification, (__bridge const void *)(self), CFSTR(“通知名“), NULL);

}

// 發(fā)送通知

- (void)postNotificaiton

{

CFNotificationCenterRef notification = CFNotificationCenterGetDarwinNotifyCenter ();

CFNotificationCenterPostNotification(notification, CFSTR(“通知名”), NULL, NULL, YES);

}

使用這個(gè)方法后,容器App調(diào)用addObserver,擴(kuò)展改變音量的時(shí)候調(diào)用 postNotificaiton ,容器App就能接收到通知了。

下一步是要傳輸數(shù)據(jù),看看發(fā)送和接收的方法的參數(shù),

void CFNotificationCenterPostNotification ( CFNotificationCenterRef center, CFStringRef name, const void *object, CFDictionaryRef userInfo, Boolean deliverImmediately );

void observerMethod (CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)

還真有 CFDictionaryRef userInfo ,太好了,趕緊爽一下!

CFNotificationCenterPostNotification(notification, CFSTR(“通知名”), NULL, 數(shù)據(jù), YES);

燃鵝!

燃鵝??!

容器App接收到的 userInfo 居然是nil??!百思不得騎姐其解,只能看文檔了:

CFNotificationCenterPostNotification 文檔

媽蛋,傳不過(guò)去!

but!無(wú)所謂啦~ㄟ( ▔, ▔ )ㄏ,一開(kāi)始不是已經(jīng)解決了共享數(shù)據(jù)的問(wèn)題了么,擴(kuò)展發(fā)送通知前先存儲(chǔ)數(shù)據(jù),容器App接收到通知后,再讀取共享數(shù)據(jù)那就好了~~~用這個(gè)思路,就能實(shí)現(xiàn)大部分的擴(kuò)展與容器App之間的交互功能了~

BTW,如果想實(shí)現(xiàn)更復(fù)雜的功能,推薦使用MMWormhole這個(gè)開(kāi)源庫(kù),它專(zhuān)門(mén)用于在Container app 與 Extension間傳遞消息,蘋(píng)果婊 Watch OS 也適用~

對(duì)了,如果用Jenkins打包,一定要注意widget 的app id (不是容器app 的app id)和app groups是正確的,否用 application loader提交程序的時(shí)候,會(huì)報(bào)錯(cuò)。

App Groups 設(shè)置

最后分享一個(gè)Podfile多個(gè)target引用部分相同pod庫(kù)的編寫(xiě)方法:

def host_pods

pod 'SSKeychain', '~> 0.1.4'

pod 'INAppStoreWindow', :head

pod 'AFNetworking', '1.1.0'

end

def shared_pods

pod 'MMWormhole','~> 2.0.0'

end

target 'HostApp' do

shared_pods

host_pods

end

target 'Extension' do

shared_pods

end


引用:

https://onevcat.com/2014/08/notification-today-widget/

https://medium.com/@saberjack/ios-sending-notifications-between-your-apps-3fe7422d6a41#.5236ab5mt

http://www.cocoachina.com/ios/20150417/11597.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容