1.擴(kuò)展是什么榄檬?
擴(kuò)展是一個(gè)特殊的程序永高。但是它并不屬于一個(gè)完整的APP恋拍,它需要有一個(gè)容器APP(containing app)來(lái)進(jìn)行發(fā)布垛孔。容器可以是一個(gè)已經(jīng)存在的APP,也可以創(chuàng)建一個(gè)新的芝囤。一個(gè)容器可以有一個(gè)或一個(gè)以上的擴(kuò)展似炎。擴(kuò)展不能獨(dú)立的進(jìn)行發(fā)布,它需要有一個(gè)容器悯姊。
擴(kuò)展被它所屬的宿主程序(host app)所加載和控制羡藐。舉個(gè)例子,它可以像Safari一樣有分享擴(kuò)張悯许,或者像通知中心的今日摘要和其他組件一樣仆嗦。系統(tǒng)的每一個(gè)支持?jǐn)U展的地方叫做擴(kuò)展插入點(diǎn)。
為了創(chuàng)建擴(kuò)展先壕,你需要添加一個(gè)target到容器(cotaining app)的工程文件之中瘩扼。Xcode提供的模板已經(jīng)包括擴(kuò)展接入點(diǎn)所需要的框架谆甜,
擴(kuò)展提供了今日擴(kuò)展接入點(diǎn),就是所謂的組件集绰,可以提供簡(jiǎn)單快捷的訪問(wèn)信息规辱。組件關(guān)聯(lián)到通知中心。設(shè)計(jì)一個(gè)簡(jiǎn)單易用的的組件是非常重要的栽燕,因?yàn)樘嗟慕换タ赡軙?huì)導(dǎo)致用戶的操作困難罕袋。要注意千萬(wàn)不要使用鍵盤。
我們都希望組件能夠有好看的界面和及時(shí)更新碍岔,保證它穩(wěn)定的工作尤為重要浴讯,你需要讓你的組件又快又節(jié)省資源。避免影響這個(gè)用戶體驗(yàn)蔼啦,系統(tǒng)將終止組件的運(yùn)行如果組件占用了太多內(nèi)存榆纽。組件需要的是簡(jiǎn)單,專注于顯示他們需要顯示的內(nèi)容捏肢。
理論說(shuō)夠了奈籽,讓我們來(lái)創(chuàng)建一個(gè)自己的今日組件,我們用它來(lái)顯示磁盤的用量鸵赫,通過(guò)一個(gè)進(jìn)度條來(lái)提示用戶唠摹。在這個(gè)過(guò)程中,我們也會(huì)涉及其他的擴(kuò)展奉瘤。
如果你想要?jiǎng)?chuàng)建一個(gè)擴(kuò)展作為一個(gè)已經(jīng)存在的app勾拉,打開Xcode工程,然后到步驟2盗温。如果你像我一樣從零開始藕赞,你需要先創(chuàng)建一個(gè)容器程序(containing app)。
打開Xcode在File目錄選擇New > Project...我們使用objective-C作為開發(fā)語(yǔ)言卖局,選擇Single View Application模板斧蜕。
打開File目錄,選擇New > Target....在Application Extension的類別中選擇Today Extension模板砚偶。
你會(huì)注意到target被添加到我們當(dāng)前的工程批销,擴(kuò)展將會(huì)被嵌入到容器程序。然后擴(kuò)展會(huì)有一個(gè)標(biāo)識(shí)符類似容器程序com.tutsplus.Today.Used-Space.
點(diǎn)擊Next, 給組件取個(gè)名字,例如Used Space, 點(diǎn)擊Finish創(chuàng)建一個(gè)target..,Xcode將為你創(chuàng)建一個(gè)新的scheme染坯,然后詢問(wèn)你是否激活均芽,點(diǎn)擊Activate繼續(xù)。
Xcode將為組件創(chuàng)建一個(gè)名叫Space Used的分組 然后在其中生成一些文件单鹿,一個(gè)UIViewController和一個(gè)storyboard掀宋,組件就只是一個(gè)UIViewController和一個(gè)storyboard,如果你打開視圖控制器的頭文件,你會(huì)發(fā)現(xiàn)它就是一個(gè)普通的UIViewController的子類劲妙。
如果你選擇擴(kuò)展的target湃鹊,打開Build Phases并展開Link Binary With Libraries,你會(huì)發(fā)現(xiàn)它添加了Notification Centre得框架镣奋。
我們現(xiàn)在來(lái)創(chuàng)建一個(gè)基本的用戶界面币呵,首先需要確定組件的大小,有兩種方式來(lái)告訴系統(tǒng)我們所需的大小侨颈。第一是是采用自動(dòng)布局(Auto Layout)富雅,第二是使用viewcontroller的preferredContentSize屬性。
自動(dòng)布局的概念在組件上依然是可用的肛搬,不僅需要注意iPhone的各種寬度(以及iPad和未來(lái)的設(shè)備)也需要注意它可能會(huì)在橫屏模式下現(xiàn)實(shí)。如果界面是用的是自動(dòng)布局來(lái)約束毕贼,對(duì)開發(fā)者來(lái)說(shuō)是非常方便的温赔。調(diào)節(jié)組件的高度可以通過(guò)setPreferredContentSize來(lái)實(shí)現(xiàn)。
在Xcode編輯器中打開MainInterface.storyboard. 你會(huì)發(fā)現(xiàn)已經(jīng)有一個(gè)HelloewWorld的label已經(jīng)在視圖中了鬼癣,選中并刪除它陶贼,因?yàn)槲覀儾粫?huì)用到,添加一個(gè)新的label并且讓它靠右對(duì)齊像圖中那樣待秃。
在Attributes Inspector中設(shè)置顏色為白色, 字體對(duì)齊方式為靠右對(duì)齊, 文本占 50.0%拜秧。
在編輯器中選擇Size to Fit Content來(lái)讓label自動(dòng)調(diào)節(jié)大小。
接著添加一個(gè)UIProgressView對(duì)象在label的左邊位置章郁,如圖所示枉氮。
選中進(jìn)度條,在Attributes Inspector改變Progress Tint為白色, 改變Track Tint為深褐色.這將會(huì)使得進(jìn)度條更加明顯暖庄,接下來(lái)添加一些約束聊替。
選中l(wèi)abel添加如圖所示的上下約束。不要勾選Constrain to margins的選項(xiàng)培廓。
選擇進(jìn)度條添加如圖所示的上左右的約束惹悄,同時(shí)不要忘記反選Constrain to margins。
因?yàn)槲覀兏淖兞思s束肩钠,所以我們會(huì)有一個(gè)小問(wèn)題需要解決泣港,現(xiàn)在的進(jìn)度條大小并不能正確的反應(yīng)約束后的進(jìn)度條大小,選擇進(jìn)度條价匠, 點(diǎn)擊Resolve Auto Layout Issues按鈕当纱,選擇Update Frames選擇Selected Views.這將會(huì)根據(jù)約束來(lái)更新進(jìn)度條的大小。
是時(shí)候來(lái)看一下我們的組件了踩窖,選擇Used Space scheme惫东,選擇Prodect目錄中得Run或者直接按下Command-R,下拉顯示通知中心,然后點(diǎn)擊下方的edit按鈕廉沮,你的今日組件將會(huì)出現(xiàn)在可添加的地方颓遏,點(diǎn)擊它左邊的添加按鈕。
這就是我們的擴(kuò)展的樣子滞时。
看起來(lái)不錯(cuò)叁幢,但是為什么在下面有那么多空隙呢?為什么操作系統(tǒng)沒有遵循進(jìn)度條的約束呢坪稽?
這個(gè)問(wèn)題都是操作系統(tǒng)的標(biāo)準(zhǔn)間距導(dǎo)致的曼玩,我們接下來(lái)將會(huì)改變它,請(qǐng)注意窒百,無(wú)論如何黍判,保證進(jìn)度條的左邊距與名字對(duì)齊的。
如果你選擇你的設(shè)備或者在其它設(shè)備運(yùn)行篙梢,你會(huì)發(fā)現(xiàn)組件會(huì)調(diào)整大小顷帖,這得益于我們使用了自動(dòng)布局( Auto Layout)。
打開TodayViewController.m. 可以看到控制器實(shí)現(xiàn)了NCWidgetProviding協(xié)議. 這意味著我們需要實(shí)現(xiàn)widgetMarginInsetsForProposedMarginInsets:方法來(lái)返回一個(gè)自定義的邊距(UIEdgeInsets結(jié)構(gòu))渤滞,修改方法如下贬墩。
再次運(yùn)行程序查看結(jié)果,發(fā)現(xiàn)組件的下邊距變小了妄呕,你可以定制這些邊距達(dá)到任意你想要的效果陶舞。
最后,我們來(lái)添加兩個(gè)Outlets绪励,打開storyboard肿孵,切換到雙編輯器模式,確笔栉海現(xiàn)實(shí)TodayViewController.m颁井。
按住Control并拖動(dòng)label到viewcontroller的接口部分來(lái)創(chuàng)建label的接口,命名為percentLabel蠢护,重復(fù)這個(gè)過(guò)程為UIProgressView創(chuàng)建barView雅宾。
我們將使用NSFileManageer類來(lái)計(jì)算設(shè)備的可用空間,那么我們改如何更新組件的數(shù)據(jù)呢葵硕?
這就就是NCWidgetProviding協(xié)議的另外一個(gè)方法眉抬。操作系統(tǒng)將調(diào)用widgetPerformUpdateWithCompletionHandler:方法當(dāng)組件被載入的時(shí)候或者從后臺(tái)進(jìn)入的時(shí)候。即使組件不可見懈凹,系統(tǒng)還是有可能載入和更新它并保存快照蜀变,快照將在組件下次出現(xiàn)的時(shí)候顯示,這是因?yàn)樵诮M件顯示之前會(huì)有一小段的時(shí)間介评。
通過(guò)處理完成句柄來(lái)判斷是否需要調(diào)用新的內(nèi)容或數(shù)據(jù)更新库北。block有一個(gè)參數(shù)NCUpdateResult來(lái)描述是否我們要更新內(nèi)容爬舰。如果不是的話,操作系統(tǒng)會(huì)知道沒有必要保存一個(gè)新的快照寒瓦。
我們需要先定義一些屬性來(lái)保存剩余情屹、已使用和總得空間,把這些屬性添加到TodayViewController.m杂腰。
創(chuàng)建一個(gè)輔助方法updateSizes來(lái)拉取必要的數(shù)據(jù)和計(jì)算設(shè)備的空間使用率垃你。
- (void)updateSizes
{
// Retrieve the attributes from NSFileManager
NSDictionary *dict = [[NSFileManager defaultManager]
attributesOfFileSystemForPath:NSHomeDirectory()
error:nil];
// Set the values
self.fileSystemSize = [[dict valueForKey:NSFileSystemSize]
unsignedLongLongValue];
self.freeSize? ? ? = [[dict valueForKey:NSFileSystemFreeSize]
unsignedLongLongValue];
self.usedSize? ? ? = self.fileSystemSize - self.freeSize;
}
我們可以方便的使用NSUserDefaults來(lái)存儲(chǔ)數(shù)據(jù)。組件的生命周期很短喂很,所以我們需要緩存這個(gè)值惜颇,我們可以給界面設(shè)置一個(gè)初始的值,然后再計(jì)算真實(shí)的值少辣。
這對(duì)我們是否要更新組件的快照是非常有幫助的凌摄,讓我們來(lái)創(chuàng)建一個(gè)快捷方法來(lái)訪問(wèn)NSUserdefaults。
注意我們使用了一個(gè)宏定義RATE_KEY,別忘了把它加入TodayViewController.m漓帅。
因?yàn)槲覀兊慕M件是一個(gè)視圖控制器锨亏,viewDidLoad方法很適合用來(lái)更新用戶界面,我們創(chuàng)建一個(gè)幫助方法updateInterface來(lái)更新界面煎殷。
可用空間的變化導(dǎo)致跟新的過(guò)于頻繁,為了判斷我們是否真的需要刷新界面腿箩,我們計(jì)算已使用量并且設(shè)置了一個(gè)更新的下限0.1%豪直,而不是一改變就刷新界面。修改widgetPerformUpdateWithCompletionHandler:如下:
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler
{
[self updateSizes];
double newRate = (double)self.usedSize / (double)self.fileSystemSize;
if (newRate - self.usedRate < 0.0001) {
completionHandler(NCUpdateResultNoData);
} else {
[self setUsedRate:newRate];
[self updateInterface];
completionHandler(NCUpdateResultNewData);
}
}
在我們每次重新計(jì)算之后珠移,如果與上次有明顯的變化弓乙,則保存值并更新界面,告訴操作系統(tǒng)钧惧。如果沒有明顯的變化暇韧,我們就不需要新的快照。如果有錯(cuò)誤發(fā)生將會(huì)在NCUpdateResultFailed來(lái)報(bào)告錯(cuò)誤的發(fā)生浓瞪,但是在這個(gè)例子中沒有出現(xiàn)錯(cuò)誤懈玻。
再一次運(yùn)行你的程序,它將正確的顯示您已經(jīng)使用了多少空間乾颁。
我們來(lái)回顧一下組件的生命周期涂乌,當(dāng)我們打開今日面板,系統(tǒng)可能會(huì)顯示上次的快照知道準(zhǔn)備完成英岭。界面會(huì)被加載湾盒,組件將取出緩存在NSUserDefaults的來(lái)更新用戶界面。
接著widgetPerformUpdateWithCompletionHandler:會(huì)被調(diào)用诅妹,重新計(jì)算真實(shí)的值罚勾,如果緩存的值和真實(shí)值相近毅人,不會(huì)發(fā)生任何事情,如果存在不同尖殃,那么新的值會(huì)被緩存界面會(huì)被刷新丈莺。
在后臺(tái)的時(shí)候,組件可能會(huì)被系統(tǒng)調(diào)用分衫,如果返回NCUpdateResultNewData场刑,新的快照就會(huì)被創(chuàng)建為了下次的出現(xiàn)。
雖然我們已經(jīng)完成了顯示使用空間的功能蚪战,它會(huì)有一個(gè)精確的數(shù)字牵现。為了避免復(fù)雜的用戶界面,使交互更加友好邀桑。如果點(diǎn)擊百分比標(biāo)簽瞎疼,組件將額外顯示一個(gè)新的標(biāo)簽。這也是一個(gè)很好的機(jī)會(huì)去學(xué)習(xí)如何在組件中使用動(dòng)畫壁畸。
打開MainInterface.storyboard選擇label贼急,在In the Attributes Inspector,在View選項(xiàng)下,找到User Interaction Enabled選項(xiàng)并啟用它捏萍。
接下來(lái)太抓,我們需要去除label的底部約束。label視圖的底部的距離會(huì)改變令杈,這意味著約束將成為無(wú)效走敌。
選擇label,在Size Inspector展開'Size'逗噩,選擇底部的空間約束掉丽,并點(diǎn)擊刪除。你也可以手動(dòng)選擇視圖中的約束引導(dǎo)并刪除它异雁。label現(xiàn)在只有頂部的約束捶障,如下圖所示。
通過(guò)點(diǎn)擊畫面上方的三個(gè)圖標(biāo)的第一個(gè)選擇視圖控制器纲刀。在Size Inspector的Size中项炼,將高度設(shè)置為106。
添加一個(gè)新的label示绊,芥挣,在Attributes Inspector設(shè)置其顏色為白色。此外耻台,設(shè)置行數(shù)3空免,高度61,寬度200盆耽。這應(yīng)該足以容納三條信息蹋砚。讓它保存靠左和靠下對(duì)齊扼菠。
最后一步是打開助理編輯和創(chuàng)建名為detailslabel的outlet。
控件將只會(huì)在一瞬間擴(kuò)大坝咐。我們可以在NSUserDefaults保存一個(gè)布爾變量來(lái)記住上一次的狀態(tài)循榆,但是,為了簡(jiǎn)單墨坚,每次組件加載它時(shí)將關(guān)閉秧饮。當(dāng)點(diǎn)擊百分比label的時(shí)候,額外的信息將會(huì)出現(xiàn)泽篮。
讓我們首先定義兩個(gè)宏在TodayViewController.m來(lái)幫助我們?cè)O(shè)置大小盗尸。 在viewDidLoad,添加兩行代碼來(lái)設(shè)置控件的初始高度帽撑,讓label透明泼各。我們將在百分比label被點(diǎn)擊的時(shí)候使詳細(xì)label出現(xiàn)。
請(qǐng)注意亏拉,我們?cè)O(shè)置控件的寬度為0扣蜻,因?yàn)閷挾葘⒂刹僮飨到y(tǒng)決定。
在詳情label中及塘,我們使用NSByteCountFormatter來(lái)展示可用莽使、已使用和總的值。添加以下代碼的視圖控制器笙僚。
-(void)updateDetailsLabel
{
NSByteCountFormatter *formatter =
[[NSByteCountFormatter alloc] init];
[formatter setCountStyle:NSByteCountFormatterCountStyleFile];
self.detailsLabel.text =
[NSString stringWithFormat:
@"Used:\t%@\nFree:\t%@\nTotal:\t%@",
[formatter stringFromByteCount:self.usedSize],
[formatter stringFromByteCount:self.freeSize],
[formatter stringFromByteCount:self.fileSystemSize]];
}
為了檢測(cè)點(diǎn)擊事件芳肌,我們?cè)谠噲D中重寫了touchesBegan:withEvent:方法。想法很簡(jiǎn)單味咳,當(dāng)檢測(cè)到觸摸事件庇勃,展開詳情標(biāo)簽并更新檬嘀。請(qǐng)注意槽驶,插件的大小的是在callingsetPreferredContentSize更新的。
雖然這個(gè)組件運(yùn)行的不錯(cuò),我們還是可以通過(guò)在收展詳情標(biāo)簽使用漸變效果來(lái)提升用戶體驗(yàn)鸳兽,這個(gè)可以通過(guò)實(shí)現(xiàn)viewWillTransitionToSize:withTransitionCoordinator:來(lái)完成掂铐。這個(gè)方法會(huì)在組件的高度變化的時(shí)候被調(diào)用。因?yàn)檫@是通過(guò)漸變協(xié)調(diào)對(duì)象來(lái)完成揍异,所以我們可以添加一些額外的動(dòng)畫全陨。
你可以看到的,我們改變了標(biāo)簽的alpha值衷掷,但是你可以添加任何類型的動(dòng)畫來(lái)增強(qiáng)的用戶體驗(yàn)辱姨。
我們?cè)僖淮芜\(yùn)行程序。通過(guò)點(diǎn)擊百分比label來(lái)現(xiàn)實(shí)詳情label戚嗅。
這些邏輯似乎過(guò)于復(fù)雜對(duì)于這樣一個(gè)簡(jiǎn)單的任務(wù)雨涛,但是你將熟悉創(chuàng)建今日擴(kuò)展的的全過(guò)程枢舶。記住這些原則在設(shè)計(jì)和開發(fā)你的插件的時(shí)候。記得要保持它的簡(jiǎn)單和明了替久,同時(shí)也別忘了性能凉泄。
緩存在一些快速操作也許不需要,但你要做耗時(shí)的處理它就變得尤為重要蚯根。用你的視圖控制器的知識(shí)來(lái)檢查它是否能夠在各種屏幕尺寸下工作后众。建議你避免滾動(dòng)視圖或復(fù)雜的觸摸識(shí)別。
雖然擴(kuò)展將有一個(gè)單獨(dú)的容器颅拦,正如我們前面看到的蒂誉,它可以使應(yīng)用程序和包含擴(kuò)展之間的數(shù)據(jù)共享。你也可以使用NSExtensionContext的OpenURL:completionhandler:通過(guò)一個(gè)自定義的URL scheme來(lái)快速啟動(dòng)你的程序從組件矩距。如果你需要分享你的延伸拗盒,創(chuàng)建一個(gè)框架來(lái)使用您的應(yīng)用程序和擴(kuò)展。
我希望這里介紹的知識(shí)是有用的锥债,幫助創(chuàng)建你的下一個(gè)偉大的今天擴(kuò)展陡蝇。