自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

iOS 通知中心擴展 制作總結(jié)與Demo

移動開發(fā) iOS
擴展 (Extension) 是 iOS 8 和 OSX 10.10 加入的一個非常大的功能點,開發(fā)者可以通過系統(tǒng)提供給我們的擴展接入點 (Extension point) 來為系統(tǒng)特定的服務(wù)提供某些附加的功能。

[[117425]]

本文是我的 WWDC 2014 筆記 中的一篇,涉及的 Session 有

Creating Extensions for iOS and OS X, Part 1

Creating Extensions for iOS and OS X, Part 2

總覽

擴展 (Extension) 是 iOS 8 和 OSX 10.10 加入的一個非常大的功能點,開發(fā)者可以通過系統(tǒng)提供給我們的擴展接入點 (Extension point) 來為系統(tǒng)特定的服務(wù)提供某些附加的功能。對于 iOS 來說,可以使用的擴展接入點有以下幾個:

1. Today 擴展 - 在下拉的通知中心的 "今天" 的面板中添加一個 widget

2. 分享擴展 - 點擊分享按鈕后將網(wǎng)站或者照片通過應(yīng)用分享

3. 動作擴展 - 點擊 Action 按鈕后通過判斷上下文來將內(nèi)容發(fā)送到應(yīng)用

4. 照片編輯擴展 - 在系統(tǒng)的照片應(yīng)用中提供照片編輯的能力

5. 文檔提供擴展 - 提供和管理文件內(nèi)容

6. 自定義鍵盤 - 提供一個可以用在所有應(yīng)用的替代系統(tǒng)鍵盤的自定義鍵盤或輸入法

系統(tǒng)為我們提供的接入點雖然還比較有限,但是不少已經(jīng)是在開發(fā)者和 iOS 的用戶中呼聲很高的了。而通過利用這些接入點提供相應(yīng)的功能,也可以極大地豐富系統(tǒng)的功能和可用性。本文將先不失一般性地介紹一下各種擴展的共通特性,然 后再以一個實際的例子著重介紹下通知中心的 Today 擴展的開發(fā)方法,以期為 iOS 8 的擴展的學(xué)習(xí)提供一個平滑的入口。

Apple 指出,iOS 8 中開發(fā)者的中心并不應(yīng)該發(fā)生改變,依然應(yīng)該是圍繞 app。在 app 中提供優(yōu)秀交互和有用的功能,現(xiàn)在是,將來也會是 iOS 應(yīng)用開發(fā)的核心任務(wù)。而擴展在 iOS 中是不能以單獨的形式存在的,也就是說我們不能直接在 AppStore 提供一個擴展的下載,擴展一定是隨著一個應(yīng)用一起打包提供的。用戶在安裝了帶有擴展的應(yīng)用后,將可以在通知中心的今日界面中,或者是系統(tǒng)的設(shè)置中來選擇開 啟還是關(guān)閉你的擴展。而對于開發(fā)者來說,提供擴展的方式是在 app 的項目中加入相應(yīng)的擴展的 target。因為擴展一般來說是展現(xiàn)在系統(tǒng)級別的 UI 或者是其他應(yīng)用中的,Apple 特別指出,擴展應(yīng)該保持輕巧迅速,并且專注功能單一,在不打擾或者中斷用戶使用當(dāng)前應(yīng)用的前提下完成自己的功能點。因為用戶是可以自己選擇禁用擴展的,所 以如果你的擴展表現(xiàn)欠佳的話,很可能會遭到用戶棄用,甚至導(dǎo)致他們將你的 app 也一并卸載。

擴展的生命周期

擴展的生命周期和包含該擴展的你的容器 app (container app) 本身的生命周期是獨立的,準確地說。它們是兩個獨立的進程,默認情況下互相不應(yīng)該知道對方的存在。擴展需要對宿主 app (host app,即調(diào)用該擴展的 app) 的請求做出相應(yīng),當(dāng)然,通過進行配置和一些手段,我們可以在擴展中訪問和共享一些容器 app 的資源,這個我們稍后再說。

因為擴展其實是依賴于調(diào)用其的宿主 app 的,因此其生命周期也是由用戶在宿主 app 中的行為所決定的。一般來說,用戶在宿主 app 中觸發(fā)了該擴展后,擴展的生命周期就開始了:比如在分享選項中選擇了你的擴展,或者向通知中心中添加了你的 widget 等等。而所有的擴展都是由 ViewController 進行定義的,在用戶決定使用某個擴展時,其對應(yīng)的 ViewController 就會被加載,因此你可以像在編寫傳統(tǒng) app 的 ViewController 那樣獲取到諸如 viewDidLoad 這樣的方法,并進行界面構(gòu)建及做相應(yīng)的邏輯。擴展應(yīng)該保持功能的單一專注,并且迅速處理任務(wù),在執(zhí)行完成必要的任務(wù),或者是在后臺預(yù)約完成任務(wù)后,一般需 要盡快通過回調(diào)將控制權(quán)交回給宿主 app,至此生命周期結(jié)束。

按照 Apple 的說法,擴展可以使用的內(nèi)存是遠遠低于 app 可以使用的內(nèi)存的。在內(nèi)存吃緊的時候,系統(tǒng)更傾向于優(yōu)先搞掉擴展,而不會是把宿主 app 殺死。因此在開發(fā)擴展的時候,也一定需要注意內(nèi)存占用的限制。另一點是比如像通知中心擴展,你的擴展可能會和其他開發(fā)人員的擴展共存,這樣如果擴展阻塞了 主線程的話,就會引起整個通知中心失去響應(yīng)。這種情況下你的擴展和應(yīng)用也就基本和用戶說再見了..

擴展和容器應(yīng)用的交互

擴展和容器應(yīng)用本身并不共享一個進程,但是作為擴展,其實是主體應(yīng)用功能的延伸,肯定不可避免地需要使用到應(yīng)用本身的邏輯甚至界面。在這種情況 下,我們可以使用 iOS 8 新引入的自制 framework 的方式來組織需要重用的代碼,這樣在鏈接 framework 后 app 和擴展就都能使用相同的代碼了。

另一個常見需求就是數(shù)據(jù)共享,即擴展和應(yīng)用互相希望訪問對方的數(shù)據(jù)。這可以通過開啟 App Groups 和進行相應(yīng)的配置來開啟在兩個進程間的數(shù)據(jù)共享。這包括了使用 NSUserDefaults 進行小數(shù)據(jù)的共享,或者使用 NSFileCoordinator 和 NSFilePresenter 甚至是 CoreData 和 SQLite 來進行更大的文件或者是更復(fù)雜的數(shù)據(jù)交互。

另外,一直以來的自定義的 url scheme 也是從擴展向應(yīng)用反饋數(shù)據(jù)和交互的渠道之一。

這些常見的手段和策略在接下來的 demo 中都會用到。一張圖片能頂千言萬語,而一個 demo 能頂千張圖片。那么,我們開始吧。

Timer Demo

Demo 做的應(yīng)用是一個簡單的計時器,即點擊開始按鈕后開始倒數(shù)計時,每秒根據(jù)剩余的時間來更新界面上的一個表示時間的 Label,然后在計時到 0 秒時彈出一個 alert,來告訴用戶時間到,當(dāng)然用戶也可以使用 Stop 按鈕來提前打斷計時。其實這個 Demo 就是我的很早之前做的一個番茄工作法的 app 的原型。

為了大家方便跟隨這個 demo,我把初始的時候的代碼放到 GitHub 的 start-project 這個 tag 上了。語言當(dāng)然是要用 Swift,界面因為不是 demo 的重點,所以就非常簡單能表明意思就好了。但是雖然簡單,卻也是利用了上一篇文章中所提到的 Size Classes 來完成的不同屏幕的布局,所以至少可以說在思想上是完備的 iOS 8 兼容了 =_=..

初始工程運行起來的界面大概是這樣的: 

初始工程

初始工程

簡單說整個項目只有一個 ViewController,點擊開始按鈕時我們通過設(shè)定希望的計時時間來創(chuàng)建一個 Timer 實例,然后調(diào)用它的 start 方法。這個方法接收兩個參數(shù),分別是每次剩余時間更新,以及計時結(jié)束(不論是計時時間到的完成還是計時被用戶打斷)時的回調(diào)方法。另外這個方法返回一個 tuple,用來表示是否開始成功以及可能的錯誤。

剩余時間更新的回調(diào)中刷新界面 UI,計時結(jié)束的回調(diào)里回收了 Timer 實例,并且顯示了一個 UIAlertController。用戶通過點擊 Stop 按鈕可以直接調(diào)用 stop 方法來打斷計時。直接簡單,沒什么其他的 trick。

我們現(xiàn)在計劃為這個 app 做一個 Today 擴展,來在通知中心中顯示并更新當(dāng)前的剩余時間,并且在計時完成后顯示一個按鈕,點擊后可以回到 app 本體,并彈出一個完成的提示。

添加擴展 Target

第一步當(dāng)然是為我們的 app 添加擴展。正如在總覽中所提到的,擴展是項目中的一個單獨的 target。在 Xcode 6 中, Apple 為我們準備了對應(yīng)各類不同擴展點的 target 模板,這使得向 app 中添加擴展非常容易。對于我們現(xiàn)在想做的 Today 擴展,只需點選菜單的 File > New > Target...,然后選擇 iOS 中的 Application Extension 的 Today Extension 就行了。 

添加 target

添加 target

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

我們的主題程序在編譯鏈接后會生成一個后綴為 .app 的包,里面包含主程序的二進制文件和各種資源。而擴展 target 將單獨生成一個后綴名為 .appex 的文件包。這個文件包將隨著主體程序被安裝,并由用戶選擇激活或者添加(對于 Today widget 的話在通知中心 Today 視圖中的編輯刪增,對于其他的擴展的話,使用系統(tǒng)的設(shè)置進行管理)。我們可以看到,現(xiàn)在項目的 Product 中已經(jīng)新增了一個擴展了。 

擴展的product

擴展的product

如果你有心已經(jīng)打開了 MainInterface 文件的話,可以注意到 Apple 已經(jīng)為我們準備了一個默認的 Hello World 的 label 了。我們這時候只要運行主程序,擴展就會一并安裝了。將 Scheme 設(shè)為 Simple Timer 的主程序,Cmd + R,然后點擊 Home 鍵將 app 切到后臺,拉下通知中心。這時候你應(yīng)該能在 Toady 視圖中找到叫做 SimpleTimerTodayExtenstion 的項目,顯示了一個 Hello World 的標簽。如果沒有的話,可以點擊下面的編輯按鈕看看是不是沒有啟用,如果在編輯菜單中也沒有的話,恭喜你遇到了和 Session 視頻里的演講者同樣的 bug,你可能需要刪除應(yīng)用,清理工程,然后再安裝試試看。一般來說卸載再安裝可以解決現(xiàn)在的 beta 版大部分的無法加載的問題,如果還是遇到問題的話,你還可以嘗試重啟設(shè)備(按照以往幾年的 SDK 的情況來看,beta 版里這很正常,正式版中應(yīng)該就沒什么問題了)。

如果一切正常的話,你能看到的通知中心應(yīng)該類似這樣: 

Hello World widget

Hello World widget

這種方式運行的擴展我們無法對其進行調(diào)試,因為我們的調(diào)試器并沒有 attach 到這個擴展的 target 上。有兩種方法讓我們調(diào)試擴展,一種是將 Scheme 設(shè)為之前 Xcode 為我們生成的 SimpleTimerTodayExtenstion,然后運行時選擇從 Today 視圖進行運行,如圖;另一種是在擴展運行時使用菜單中的 Debug > Attach to Process > By Process Identifier (PID) or name,然后輸入你的擴展的名字(在我們的 demo 中是 com.onevcat.SimpleTimer.SimpleTimerTodayExtension)來把調(diào)試器掛載到進程上去。 

調(diào)試擴展

調(diào)試擴展

#p#

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

擴展既然是個 ViewController,那各種連接 IBOutlet,使用 viewDidLoad 之類的生命周期方法來設(shè)置 UI 什么的自然不在話下。我們現(xiàn)在的第一個難點就是,如何獲取應(yīng)用主體在退出時計時器的剩余時間。只要知道了還剩多久以及何時退出,我們就能在通知中心中顯示 出計時器正確的剩余時間了。

對 iOS 開發(fā)者來說,沙盒限制了我們在設(shè)備上隨意讀取和寫入。但是對于應(yīng)用和其對應(yīng)的擴展來說,Apple 在 iOS 8 中為我們提供了一種可能性,那就是 App Groups。App Groups 為同一個 vender 的應(yīng)用或者擴展定義了一組域,在這個域中同一個 group 可以共享一些資源。對于我們的例子來說,我們只需要使用同一個 group 下的 NSUserDefaults 就能在主體應(yīng)用不活躍時向其中存儲數(shù)據(jù),然后在擴展初始化時從同一處進行讀取就行了。

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

啟用 App Groups

啟用 App Groups

然后讓我們開始寫代碼吧!首先是在主體程序的 ViewController.swift 中添加一個程序失去前臺的監(jiān)聽,在 viewDidLoad 中加入:

  1. NSNotificationCenter.defaultCenter()   
  2.     .addObserver(self, selector: "applicationWillResignActive",name: UIApplicationWillResignActiveNotification, object: nil) 

然后是所調(diào)用的 applicationWillResignActive 方法:

  1. @objc private func applicationWillResignActive() { 
  2.     if timer == nil { 
  3.         clearDefaults() 
  4.     } else { 
  5.         if timer.running { 
  6.             saveDefaults() 
  7.         } else { 
  8.             clearDefaults() 
  9.         } 
  10.     } 
  11.   
  12. private func saveDefaults() {   
  13.     let userDefault = NSUserDefaults(suiteName: "group.simpleTimerSharedDefaults"
  14.     userDefault.setInteger(Int(timer.leftTime), forKey: "com.onevcat.simpleTimer.lefttime"
  15.     userDefault.setInteger(Int(NSDate().timeIntervalSince1970), forKey: "com.onevcat.simpleTimer.quitdate"
  16.   
  17.     userDefault.synchronize() 
  18.   
  19. private func clearDefaults() {   
  20.     let userDefault = NSUserDefaults(suiteName: "group.simpleTimerSharedDefaults"
  21.     userDefault.removeObjectForKey("com.onevcat.simpleTimer.lefttime"
  22.     userDefault.removeObjectForKey("com.onevcat.simpleTimer.quitdate"
  23.   
  24.     userDefault.synchronize() 

這樣,在應(yīng)用切到后臺時,如果正在計時,我們就將當(dāng)前的剩余時間和退出時的日期存到了 NSUserDefaults 中。這里注意,可能一般我們在使用 NSUserDefaults 時更多地是使用 standardUserDefaults,但是這里我們需要這兩個數(shù)據(jù)能夠被擴展訪問到的話,我們必須使用在 App Groups 中定義的名字來使用 NSUserDefaults。

接下來,我們可以到擴展的 TodayViewController.swift 中去獲取這些數(shù)據(jù)了。在擴展 ViewController 的 viewDidLoad 中,添加以下代碼:

  1. let userDefaults = NSUserDefaults(suiteName: "group.simpleTimerSharedDefaults")   
  2. let leftTimeWhenQuit = userDefaults.integerForKey("com.onevcat.simpleTimer.lefttime")   
  3. let quitDate = userDefaults.integerForKey("com.onevcat.simpleTimer.quitdate"
  4.  
  5. let passedTimeFromQuit = NSDate().timeIntervalSinceDate(NSDate(timeIntervalSince1970: NSTimeInterval(quitDate))) 
  6.  
  7. let leftTime = leftTimeWhenQuit - Int(passedTimeFromQuit) 
  8.  
  9. lblTImer.text = "\(leftTime)"   

當(dāng)然別忘了把 StoryBoard 的那個 label 拖出來:

  1. @IBOutlet weak var lblTImer: UILabel! 

再次運行程序,并開始一個計時,然后按 Home 鍵切到后臺,拉出通知中心,perfect,我們的擴展能夠和主程序進行數(shù)據(jù)交互了: 

讀取數(shù)據(jù)

讀取數(shù)據(jù)

在應(yīng)用和擴展間共享代碼 - Framework

接下來的任務(wù)是在 Today 界面中進行計時,來刷新我們的界面。這部分代碼其實我們已經(jīng)寫過(當(dāng)然..確切來說是我寫的,你可能只是看過),沒錯,就是應(yīng)用中的 Timer.swift 文件。我們只需要在擴展的 ViewController 中用剩余時間創(chuàng)建一個 Timer 的實例,然后在更新的 callback 里設(shè)置 label 就好了嘛。但是問題是,這部分代碼是在應(yīng)用中的,我們要如何在擴展中也能使用它呢?

一個最直接也是最簡單的想法自然是把 Timer.swift 加入到擴展 target 的編譯文件中去,這樣在擴展中自然也就可以使用了。但是 iOS 8 開始 Apple 為我們提供了一個更好的選擇,那就是做成 Framework。單個文件可能不會覺得有什么差別,但是隨著需要共用的文件數(shù)量和種類的增加,將單個文件逐一添加到不同 target 這種管理方法很快就會將事情弄成一團亂麻。你需要考慮每一個新加或者刪除的文件影響的范圍,以及它們分別需要適用何處,這簡直就是人間地獄。提供一個統(tǒng)一 漂亮的 framework 會是更多人希望的選擇(其實也差不多成為事實標準了)。使用 framework 進行模塊化的另一個好處是可以得益于良好的訪問控制,以保證你不會接觸到不應(yīng)該使用的東西,然后,Swift 的 namespace 是基于模塊的,因此你也不再需要擔(dān)心命名沖突等等一攤子 objc 時代的煩心事兒。

現(xiàn)在讓我們把 Timer.swift 放到 framework 里吧。首先我們新建一個 framework 的 target。File > New > Target... 中選擇 Framework & Library,選中 Cocoa Touch Framework (配圖中的另外幾個選項可能在你的 Xcode 中是沒有的,請無視它們,這是歷史遺留問題),然后確定。按照 Apple 對 framework 的命名規(guī)范,也許 SimpleTimerKit 會是一個不錯的名字。 

建立框架

建立框架

接下來,我們將 Timer.swift 從應(yīng)用中移動到 framework 中。很簡單,首先將其從應(yīng)用的 target 中移除,然后加入到新建的 SimpleTimerKit 的 Compile Sources 中。

添加 framework 文件

添加 framework 文件

確認在應(yīng)用中 link 了新的 framwork,并且在 ViewController.swift 中加上 import SimpleTimerKit 后試著編譯看看...好多錯誤,基本都是 ViewController 中說找不到 Timer 之類的。這是因為原來的實現(xiàn)是在同一個 module 中的,默認的 internal 的訪問層級就可以讓 ViewController 訪問到關(guān)于 Timer 和相應(yīng)方法的信息。但是現(xiàn)在它們處于不同的 module 中,所以我們需要對 Timer.swift 的訪問權(quán)限進行一些修改,在需要外部訪問的地方加上 public 關(guān)鍵字。關(guān)于 Swift 中的訪問控制,可以參考 Apple 關(guān)于 Swift 的這篇官方博客,簡單說就是 private 只允許本文件訪問,不寫的話默認是 internal,允許統(tǒng)一 module 訪問,而要提供給別的 module 使用的話,需要聲明為 public。修改后的 Timer.swift 文件大概是這個樣子的。

修改合適的訪問權(quán)限后,接下來我們就可以將這個 framework 鏈接到擴展的 target 了。鏈接以后編譯什么的可以通過,但是會多一個警告: 

警告

警告

這是因為作為插件,需要遵守更嚴格的沙盒限制,所以有一些 API 是不能使用的。為了避免這個警告,我們需要在 framework 的 target 中聲明在我們使用擴展可用的 API。具體在 SimpleTimerKit 的 target 的 General 選項卡中,將 Deployment Info 中的 Allow app extension API only 勾選上就可以了。關(guān)于在擴展里不能使用的 API,都已經(jīng)被 Apple 標上了 NS_EXTENSION_UNAVAILABLE,在這里有一份簡單的列表可供參考,基本來說都是 runtime 的東西以及一些會讓用戶迷惑或很危險的操作(當(dāng)然這個標記的方法很可能會不斷變動,最終一切請以 Apple 的文檔和實際代碼為準)。 

開啟 Extension Only

開啟 Extension Only

接下來,在擴展的 ViewController 中也鏈接 SimpleTimerKit 并加入 import SimpleTimerKit,我們就可以在擴展中使用 Timer 了。將剛才的直接設(shè)置 label 的代碼去掉,換成下面的:

  1. override func viewDidLoad() {   
  2.     //... 
  3.   
  4.     if (leftTime > 0) { 
  5.         timer = Timer(timeInteral: NSTimeInterval(leftTime)) 
  6.         timer.start(updateTick: { 
  7.                 [weak self] leftTick in self!.updateLabel() 
  8.             }, stopHandler: nil) 
  9.     } else { 
  10.         //Do nothing now 
  11.     } 
  12.   
  13. private func updateLabel() {   
  14.     lblTimer.text = timer.leftTimeString 

我們在擴展里也像在 app 內(nèi)一樣,創(chuàng)建 Timer,給定回調(diào),坐等界面刷新。運行看看,先進入應(yīng)用,開始一個計時。然后退出,打開通知中心。通知中心中現(xiàn)在也開始計時了,而且確實是從剩余的時間開始的,一切都很完美: 

通知中心計時

通知中心計時

#p#

通過擴展啟動主體應(yīng)用

最后一個任務(wù)是,我們想要在通知中心計時完畢后,在擴展上呈現(xiàn)一個 "完成啦" 的按鈕,并通過點擊這個按鈕能回到應(yīng)用,并在應(yīng)用內(nèi)彈出結(jié)束的 alert。

這其實最關(guān)鍵的在于我們要如何啟動主體容器應(yīng)用,以及向其傳遞數(shù)據(jù)??赡芎芏嗤瑢W(xué)會想到 URL Scheme,沒錯通過 URL Scheme 我們確實可以啟動特定應(yīng)用并攜帶數(shù)據(jù)。但是一個問題是為了通過 URL 啟動應(yīng)用,我們一般需要調(diào)用 UIApplication 的 openURL 方法。如果細心的剛才看了 NS_EXTENSION_UNAVAILABLE 的同學(xué)可能會發(fā)現(xiàn)這個方法是被禁用的(這也是很 make sense 的一件事情,因為說白了擴展通過 sharedApplication 拿到的其實是宿主應(yīng)用,宿主應(yīng)用表示憑什么要讓你拿到?。。榱送瓿赏瑯拥牟僮?,Apple 為擴展提供了一個 NSExtensionContext 類來與宿主應(yīng)用進行交互。用戶在宿主應(yīng)用中啟動擴展后,宿主應(yīng)用提供一個上下文給擴展,里面最主要的是包含了 inputItems 這樣的待處理的數(shù)據(jù)。當(dāng)然對我們現(xiàn)在的需求來說,我們只要用到它的 openURL(URL:,completionHandler:) 方法就好了。

另外,我們可能還需要調(diào)整一下擴展 widget 的尺寸,以讓我們有更多的空間顯示按鈕,這可以通過設(shè)定 preferredContentSize 來做到。在 TodayViewController.swift 中加入以下方法:

  1. private func showOpenAppButton() {   
  2.     lblTimer.text = "Finished" 
  3.     preferredContentSize = CGSizeMake(0, 100) 
  4.   
  5.     let button = UIButton(frame: CGRectMake(0, 50, 50, 63)) 
  6.     button.setTitle("Open", forState: UIControlState.Normal) 
  7.     button.addTarget(self, action: "buttonPressed:", forControlEvents: UIControlEvents.TouchUpInside) 
  8.   
  9.     view.addSubview(button)         

在設(shè)定 preferredContentSize 時,指定的寬度都是無效的,系統(tǒng)會自動將其處理為整屏的寬度,所以扔個 0 進去就好了。在這里添加按鈕時我偷了個懶,本來應(yīng)該使用Auto Layout 和添加約束的,但是這并不是我們這個 demo 的重點。另一方面,為了代碼清晰明了,就直接上坐標了。

然后添加這個按鈕的 action:

  1. @objc private func buttonPressed(sender: AnyObject!) { 
  2.     extensionContext.openURL(NSURL(string: "simpleTimer://finished"), completionHandler: nil) 

我們將傳遞的 URL 的 scheme 是 simpleTimer,以 host 的 finished 作為參數(shù),就可以通知主體應(yīng)用計時完成了。然后我們需要在計時完成時調(diào)用 showOpenAppButton 來顯示按鈕,更新 viewDidLoad 中的內(nèi)容:

  1. override func viewDidLoad() {   
  2.     //... 
  3.     if (leftTime > 0) { 
  4.         timer = Timer(timeInteral: NSTimeInterval(leftTime)) 
  5.         timer.start(updateTick: { 
  6.             [weak self] leftTick in self!.updateLabel() 
  7.             }, stopHandler: { 
  8.                 [weak self] finished in self!.showOpenAppButton() 
  9.             }) 
  10.     } else { 
  11.         showOpenAppButton() 
  12.     } 

最后一步是在主體應(yīng)用的 target 里設(shè)置合適的 URL Scheme: 

設(shè)置 url scheme

設(shè)置 url scheme

然后在 AppDelegate.swift 中捕獲這個打開事件,并檢測計時是否完成,然后做出相應(yīng):

  1. func application(application: UIApplication!, openURL url: NSURL!, sourceApplication: String!, annotation: AnyObject!) -> Bool {   
  2.     if url.scheme == "simpleTimer" { 
  3.         if url.host == "finished" { 
  4.             NSNotificationCenter.defaultCenter() 
  5.                 .postNotificationName(taskDidFinishedInWidgetNotification, object: nil) 
  6.         } 
  7.         return true 
  8.     } 
  9.   
  10.     return false 

在這個例子里,我們發(fā)了個通知。而在 ViewController 中我們可以一開始就監(jiān)聽這個通知,然后收到后停止計時并彈出提示就行了。當(dāng)然我們可能需要一些小的重構(gòu),比如添加是手動打斷還是計時完成的判斷以彈出不一樣的對話框等等,這些都很簡單再次就不贅述了。 

完成

完成

至此,我們就完成了一個很基本的通知中心擴展,完整的項目可以在 GitHub repo 的 master 上找到。這個計時器現(xiàn)在在應(yīng)用中只在前臺或者通知中心顯示時工作,如果你退出應(yīng)用后再打開應(yīng)用,其實這段時間內(nèi)是沒有計時的。因此這個項目之后可能的改進 就是在返回應(yīng)用的時候添加一下計時的判定,來更新計時器的剩余時間,或者是已經(jīng)完成了的話就直接結(jié)束計時。

其他

其實在 Xcode 為我們生成的模板文件中,還有這么一段代碼也很重要:

  1. func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) {   
  2.     // Perform any setup necessary in order to update the view. 
  3.   
  4.     // If an error is encoutered, use NCUpdateResult.Failed 
  5.     // If there's no update required, use NCUpdateResult.NoData 
  6.     // If there's an update, use NCUpdateResult.NewData 
  7.   
  8.     completionHandler(NCUpdateResult.NewData) 

對于通知中心擴展,即使你的擴展現(xiàn)在不可見 (也就是用戶沒有拉開通知中心),系統(tǒng)也會時不時地調(diào)用實現(xiàn)了 NCWidgetProviding 的擴展的這個方法,來要求擴展刷新界面。這個機制和 iOS 7 引入的后臺機制是很相似的。在這個方法中我們一般可以做一些像 API 請求之類的事情,在獲取到了數(shù)據(jù)并更新了界面,或者是失敗后都使用提供的 completionHandler 來向系統(tǒng)進行報告。

值得注意的一點是 Xcode (至少現(xiàn)在的 beta 4) 所提供的模板文件的 ViewController 里雖然有這個方法,但是它默認并沒有 conform 這個接口,所以要用的話,我們還需要在類聲明時加上 NCWidgetProviding。

總結(jié)

這個 Demo 主要涉及了通知中心的 Toady widget 的添加和一般交互。其實擴展是一個相當(dāng)大塊的內(nèi)容,對于其他像是分享或者是 Action 的擴展,其使用方式又會有所不同。但是核心的概念,生命周期以及與本體應(yīng)用交互的方法都是相似的。Xcode 在我們創(chuàng)建擴展時就為我們提供了非常好的模版文件,更多的時候我們要做的只不過是在相應(yīng)的方法內(nèi)填上我們的邏輯,而對于配置方面基本不太需要操心,這一點 還是非常方便的。

就為了擴展這個功能,我已經(jīng)迫不及待地想用上 iOS 8 了..不論是使用別人開發(fā)的擴展還是自己開發(fā)方便的擴展,都會讓這個世界變得更美好。

原文取自:OneV's Den的博客

責(zé)任編輯:閆佳明 來源: onevcat.com
相關(guān)推薦

2021-04-23 14:07:55

iOS 15iPhone界面

2012-03-30 10:37:05

數(shù)據(jù)中心擴展服務(wù)器

2013-06-14 11:21:43

iOS開發(fā)移動開發(fā)畫圖

2011-07-22 16:47:53

iOS 通知 xcode

2022-02-10 20:09:24

Dubbo源碼Provider

2017-12-18 11:09:45

消息轉(zhuǎn)發(fā)DemoPerson

2022-05-15 15:14:04

爬蟲Requests反爬

2012-03-09 13:52:28

Adob??e AIRiOS

2009-12-04 15:43:03

PHP JSON擴展

2014-07-11 09:33:24

iOS 8動作擴展

2015-09-23 10:14:48

iOS 代碼實踐

2014-11-04 10:38:13

iOS圖形

2013-09-12 15:37:09

iOS開發(fā)流程

2019-05-07 16:15:06

iOS系統(tǒng)系統(tǒng)通知庫通知消息

2022-05-19 19:14:30

數(shù)據(jù)中心縱向擴展橫向擴展

2011-07-22 16:57:44

iOS 通知

2015-03-09 09:44:35

GmailiOS

2013-07-10 10:02:27

Chrome 28瀏覽器

2011-10-13 15:46:48

通知中心手機

2021-12-06 11:26:05

Windows 11飛行模式通知中心
點贊
收藏

51CTO技術(shù)棧公眾號