iOS today extension (swift) 教學筆記

iOS的「今日小工具」(today widget)的用法

使用語言: Swift4,XCode版本: 9.4,模擬器版本: iOS11

today extension是iOS8就有的東西,不過之前一直沒用到

近期想寫個待辦事項的app,那就順便練習一下today widget吧

此筆記為記錄以下功能

1. 怎麼從專案中新增today widget

2. today extension的UI

3. today伸縮的按鈕

4. 從today中啟動App

5. today widget與APP之類如何傳遞資料(UserDefault)

1. 怎麼從專案中新增today widget

首先先新增一個空白專案

接下來再新增一個target(File -> New -> Target)

再選擇Today Extension,會發現裡頭非常多extension種類,可以善用filter,直接打上「today」就只剩一個了(請見附圖)

選擇today,再點選「Next」後會出現詢問視窗,直接點選「Activate」即可

新增完畢

在filter打上today, 就剩下一個了!

這時候會發現專案的資料夾不一樣,而且執行的App的圖示也不同。

由這種資料夾分類方式也可看出,Apple不希望開發者把兩個搞混在一起。

App為一包資料夾,而Widget又是另外一包資料夾,其中Storyboard也是分開的。

基本上可以把App與Widget想成是兩件不一樣的事,這樣開發的時候比較不會有「啊~~怎麼傳個東西這麼麻煩」的感覺

如果你從E直接執行,就會發現畫面是停在Today的小工具頁。畫面應該會如下圖,有一個Widget,並且內容是白字的Hello World

2. today extension的UI

打開Widget的Storyboard,你可以發現只有一個ViewController,並且畫面超級小,也看不到那個Label在哪。

原因是Label預設是白字的(所以你執行起來才會看到白字的Hello World),而背景Default是透明,所以你是看不到Label的。

你可以先把ViewController放大(標示4、5處),你可以直接把Freeform改成Fix,這樣畫面就清楚多了,再將Label改成黑字

Apple滿建議直接用Autolayout的,因此就像你平常一樣使用Storyboard的方式一樣操作吧。

注意事項

比較要注意的是Widget並不建議使用滾動(你故意用也可以,你就會發現怎麼沒有正常滾動)。

也就是說ScrollView / TableView / CollectionView / Slider…這種東西都不能用,其實也是很好理解的,畢竟Today可以新增非常多個Widget,萬一每個Widget都可以上下滑,使用者就崩潰了;同理左右滑也一樣,到底是滑Widget還是在滑App的左右頁呢?

除了滑算以外,不應該有任何需要「輸入」的操作,也就是說不該發生彈出鍵盤的操作,也很好理解,大多使用者已經習慣「點畫面任一處,即縮下鍵盤」,但畫面通通都是Widget,點下去多半會觸發其它動作,這樣會非常容易讓使用者崩潰。

因此要記得「不要使用到任何操作滑動的元件」以及「不要輸入的操作」。

3. today伸縮的按鈕

如果有在用Today Widget的話,應該會注意到有些App可以切換展開與折合模式。(顯示更多 / 顯示更少)

這邊需要寫到程式碼,在Widget資料夾的TodayViewController中新增以下程式碼

下圖中的標號「3」(或見16行),以及標號「4」(或見20~25行)

寫了標號「3」,直接執行你會發現有出現按鈕,按了卻不會有反應

需要標號「4」,寫出模式改變時,整體的大小也需要改變,其中第22行我寫了高度要500,其實他有限制最大高度,似乎就是「手機的最大高度」,也就是就算我寫了「9999」,他也就只有手機那麼高而已了。

另外可以注意到第12行,他除了繼承一般的UIViewController以外,也有NCWidgetProviding,所以才有標題「3、4」這兩種方法。

附上程式碼,比較方便貼(貼上XCode後,再將貼上的程式碼反白選取,並且按下control+I即可自動縮排)

override func viewDidLoad() {
super.viewDidLoad()
self.extensionContext?.widgetLargestAvailableDisplayMode = .expanded
}
func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
if activeDisplayMode == .expanded {
self.preferredContentSize = CGSize(width: maxSize.width, height: 500)
}else {
self.preferredContentSize = maxSize
}
}

4. 從today中啟動App

接下來,我們直接在Widget的Storyboard直接新增一個按鈕,並且拉線,準備要做點按鈕,開啟App的行為。

在寫程式之前,必須先新增Schemes,請參考以下附圖的標號順序

第6步的URL Scheme請你自己記好你寫的

接下來再回到TodayViewController,在IBAction點下的動作中寫下以下程式碼

要注意的是url是「YOUR_SCHEMES://」,不要貼的太開心,只貼到「SCHEMES」,否則點下按鈕會出現以下錯誤訊息

[_NCWidgetExtensionContext openURL:completionHandler:]_block_invoke failed: Error Domain=NSOSStatusErrorDomain Code=-50 "(null)"

他會說找不到你要他開啟的Url

一樣,附上程式碼比較好貼

let url = URL(string: "YOUR_SCHEMES_NAME://")!
self.extensionContext?.open(url, completionHandler: nil)

點下去之後應該就會出現一片白,是正常的。畢竟我們App自從新增專案後,就再也沒有去修改,因此預設畫面的確是全白。

5. today widget與APP之類如何傳遞資料(UserDefault)

這邊先介紹UserDefault,CoreData需要研究一下3口3

首先,先拉元件。

我們在App在Storyboard拉出TextField以及按鈕,按鈕負責存下TextField所輸入的內容;並在Widget的Storyboard中拉出一個Label,負責顯示App中所儲存的內容。

再來,需要讓App與Widget互相認識。

請參考以下附圖的標號流程操作,簡言之就是去把App Groups打開並新增Groups。

這裡有兩點要注意

  1. 點選標號6的「+」後,AppGroup的名稱不是隨便亂打,是打你的bundleID,bundleID可參考下圖標號流程

2.我框選的那兩個打勾圖示,是因為我這次做這個功能時,剛好Apple權限有修改,因此他原本是出現錯誤提示的。這時候只要去Itunes點選同意權限即可。

接下來,新增完App的Groups之後,一樣Widget也要重複操作一次。請參考下圖標號順序操作。這時候你應該不需要再打一次BundleID,而是可以直接勾選了。

好了,以上步驟即可完成讓他們互相認識的依據了。

最後,寫程式

分成兩部份,分法同UI,一個是App;另一個是Widget

先是App類,

在15行全域的部份先宣告UserDefault,其中要注意的是suitName就是你剛剛寫的AppGroup,並不是亂打的。

接下來IBAction的點選就負責把資料存起來。

一樣,附上程式碼比較好貼

let myUserDefault = UserDefaults(suiteName: "group.YOUR_BUNDLE_ID")
myUserDefault?.setValue(textField.text, forKey: "test")

App部份即完成了。

再來是Widget部份

基本上是跟App一樣的,開一個全域的UserDefault,接下來畫面要出現時再讀出UserDefault所儲存的內容。

直接執行後,即可完成。