1.擴(kuò)展是什么?
擴(kuò)展是一個(gè)特殊的程序。但是它并不屬于一個(gè)完整的APP,它需要有一個(gè)容器APP(containing app)來進(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)單快捷的訪問信息。組件關(guān)聯(lián)到通知中心。設(shè)計(jì)一個(gè)簡(jiǎn)單易用的的組件是非常重要的,因?yàn)樘嗟慕换タ赡軙?huì)導(dǎo)致用戶的操作困難。要注意千萬不要使用鍵盤。
我們都希望組件能夠有好看的界面和及時(shí)更新,保證它穩(wěn)定的工作尤為重要,你需要讓你的組件又快又節(jié)省資源。避免影響這個(gè)用戶體驗(yàn),系統(tǒng)將終止組件的運(yùn)行如果組件占用了太多內(nèi)存。組件需要的是簡(jiǎn)單,專注于顯示他們需要顯示的內(nèi)容。
理論說夠了,讓我們來創(chuàng)建一個(gè)自己的今日組件,我們用它來顯示磁盤的用量,通過一個(gè)進(jìn)度條來提示用戶。在這個(gè)過程中,我們也會(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ā)語言,選擇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,然后詢問你是否激活,點(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)在來創(chuàng)建一個(gè)基本的用戶界面,首先需要確定組件的大小,有兩種方式來告訴系統(tǒng)我們所需的大小。第一是是采用自動(dòng)布局(Auto Layout),第二是使用viewcontroller的preferredContentSize屬性。
自動(dòng)布局的概念在組件上依然是可用的,不僅需要注意iPhone的各種寬度(以及iPad和未來的設(shè)備)也需要注意它可能會(huì)在橫屏模式下現(xiàn)實(shí)。如果界面是用的是自動(dòng)布局來約束,對(duì)開發(fā)者來說是非常方便的。調(diào)節(jié)組件的高度可以通過setPreferredContentSize來實(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來讓label自動(dòng)調(diào)節(jié)大小。
接著添加一個(gè)UIProgressView對(duì)象在label的左邊位置,如圖所示。
選中進(jìn)度條,在Attributes Inspector改變Progress Tint為白色, 改變Track Tint為深褐色.這將會(huì)使得進(jìn)度條更加明顯,接下來添加一些約束。
選中l(wèi)abel添加如圖所示的上下約束。不要勾選Constrain to margins的選項(xiàng)。
選擇進(jìn)度條添加如圖所示的上左右的約束,同時(shí)不要忘記反選Constrain to margins。
因?yàn)槲覀兏淖兞思s束,所以我們會(huì)有一個(gè)小問題需要解決,現(xiàn)在的進(jìn)度條大小并不能正確的反應(yīng)約束后的進(jìn)度條大小,選擇進(jìn)度條, 點(diǎn)擊Resolve Auto Layout Issues按鈕,選擇Update Frames選擇Selected Views.這將會(huì)根據(jù)約束來更新進(jìn)度條的大小。
是時(shí)候來看一下我們的組件了,選擇Used Space scheme,選擇Prodect目錄中得Run或者直接按下Command-R,下拉顯示通知中心,然后點(diǎn)擊下方的edit按鈕,你的今日組件將會(huì)出現(xiàn)在可添加的地方,點(diǎn)擊它左邊的添加按鈕。
這就是我們的擴(kuò)展的樣子。
看起來不錯(cuò),但是為什么在下面有那么多空隙呢?為什么操作系統(tǒng)沒有遵循進(jìn)度條的約束呢?
這個(gè)問題都是操作系統(tǒng)的標(biāo)準(zhǔn)間距導(dǎo)致的,我們接下來將會(huì)改變它,請(qǐng)注意,無論如何,保證進(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:方法來返回一個(gè)自定義的邊距(UIEdgeInsets結(jié)構(gòu)),修改方法如下。
再次運(yùn)行程序查看結(jié)果,發(fā)現(xiàn)組件的下邊距變小了,你可以定制這些邊距達(dá)到任意你想要的效果。
最后,我們來添加兩個(gè)Outlets,打開storyboard,切換到雙編輯器模式,確?,F(xiàn)實(shí)TodayViewController.m。
按住Control并拖動(dòng)label到viewcontroller的接口部分來創(chuàng)建label的接口,命名為percentLabel,重復(fù)這個(gè)過程為UIProgressView創(chuàng)建barView。
我們將使用NSFileManageer類來計(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í)間。
通過處理完成句柄來判斷是否需要調(diào)用新的內(nèi)容或數(shù)據(jù)更新。block有一個(gè)參數(shù)NCUpdateResult來描述是否我們要更新內(nèi)容。如果不是的話,操作系統(tǒng)會(huì)知道沒有必要保存一個(gè)新的快照。
我們需要先定義一些屬性來保存剩余、已使用和總得空間,把這些屬性添加到TodayViewController.m。
創(chuàng)建一個(gè)輔助方法updateSizes來拉取必要的數(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來存儲(chǔ)數(shù)據(jù)。組件的生命周期很短,所以我們需要緩存這個(gè)值,我們可以給界面設(shè)置一個(gè)初始的值,然后再計(jì)算真實(shí)的值。
這對(duì)我們是否要更新組件的快照是非常有幫助的,讓我們來創(chuàng)建一個(gè)快捷方法來訪問NSUserdefaults。
注意我們使用了一個(gè)宏定義RATE_KEY,別忘了把它加入TodayViewController.m。
因?yàn)槲覀兊慕M件是一個(gè)視圖控制器,viewDidLoad方法很適合用來更新用戶界面,我們創(chuàng)建一個(gè)幫助方法updateInterface來更新界面。
可用空間的變化導(dǎo)致跟新的過于頻繁,為了判斷我們是否真的需要刷新界面,我們計(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來報(bào)告錯(cuò)誤的發(fā)生,但是在這個(gè)例子中沒有出現(xiàn)錯(cuò)誤。
再一次運(yùn)行你的程序,它將正確的顯示您已經(jīng)使用了多少空間。
我們來回顧一下組件的生命周期,當(dāng)我們打開今日面板,系統(tǒng)可能會(huì)顯示上次的快照知道準(zhǔn)備完成。界面會(huì)被加載,組件將取出緩存在NSUserDefaults的來更新用戶界面。
接著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)并啟用它。
接下來,我們需要去除label的底部約束。label視圖的底部的距離會(huì)改變,這意味著約束將成為無效。
選擇label,在Size Inspector展開'Size',選擇底部的空間約束,并點(diǎn)擊刪除。你也可以手動(dòng)選擇視圖中的約束引導(dǎo)并刪除它。label現(xiàn)在只有頂部的約束,如下圖所示。
通過點(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è)布爾變量來記住上一次的狀態(tài),但是,為了簡(jiǎn)單,每次組件加載它時(shí)將關(guān)閉。當(dāng)點(diǎn)擊百分比label的時(shí)候,額外的信息將會(huì)出現(xiàn)。
讓我們首先定義兩個(gè)宏在TodayViewController.m來幫助我們?cè)O(shè)置大小。 在viewDidLoad,添加兩行代碼來設(shè)置控件的初始高度,讓label透明。我們將在百分比label被點(diǎn)擊的時(shí)候使詳細(xì)label出現(xiàn)。
請(qǐng)注意,我們?cè)O(shè)置控件的寬度為0,因?yàn)閷挾葘⒂刹僮飨到y(tǒng)決定。
在詳情label中,我們使用NSByteCountFormatter來展示可用、已使用和總的值。添加以下代碼的視圖控制器。
-(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ò),我們還是可以通過在收展詳情標(biāo)簽使用漸變效果來提升用戶體驗(yàn),這個(gè)可以通過實(shí)現(xiàn)viewWillTransitionToSize:withTransitionCoordinator:來完成。這個(gè)方法會(huì)在組件的高度變化的時(shí)候被調(diào)用。因?yàn)檫@是通過漸變協(xié)調(diào)對(duì)象來完成,所以我們可以添加一些額外的動(dòng)畫。
你可以看到的,我們改變了標(biāo)簽的alpha值,但是你可以添加任何類型的動(dòng)畫來增強(qiáng)的用戶體驗(yàn)。
我們?cè)僖淮芜\(yùn)行程序。通過點(diǎn)擊百分比label來現(xiàn)實(shí)詳情label。
這些邏輯似乎過于復(fù)雜對(duì)于這樣一個(gè)簡(jiǎn)單的任務(wù),但是你將熟悉創(chuàng)建今日擴(kuò)展的的全過程。記住這些原則在設(shè)計(jì)和開發(fā)你的插件的時(shí)候。記得要保持它的簡(jiǎn)單和明了,同時(shí)也別忘了性能。
緩存在一些快速操作也許不需要,但你要做耗時(shí)的處理它就變得尤為重要。用你的視圖控制器的知識(shí)來檢查它是否能夠在各種屏幕尺寸下工作。建議你避免滾動(dòng)視圖或復(fù)雜的觸摸識(shí)別。
雖然擴(kuò)展將有一個(gè)單獨(dú)的容器,正如我們前面看到的,它可以使應(yīng)用程序和包含擴(kuò)展之間的數(shù)據(jù)共享。你也可以使用NSExtensionContext的OpenURL:completionhandler:通過一個(gè)自定義的URL scheme來快速啟動(dòng)你的程序從組件。如果你需要分享你的延伸,創(chuàng)建一個(gè)框架來使用您的應(yīng)用程序和擴(kuò)展。
我希望這里介紹的知識(shí)是有用的,幫助創(chuàng)建你的下一個(gè)偉大的今天擴(kuò)展。