下載 Xcode 8,配置 iOS 10 和 Swift 3html
(可選)經過命令行編譯ios
除 非你想使用命令行編譯,使用 Swift 3.0 的工具鏈並不須要對項目作任何改變。若是你想的話,打開 Xcode-beta,而後從頂部菜單欄中選擇 Xcode > Preferences,接着選擇 Location,在頁面的底部,你會看到「Command Line Tool」這行設置,請在這裏選擇 Xcode 8.0。swift
如今,在 Terminal 使用命令行找到工程所在的文件夾,調用 xcodebuild 命令就能夠編譯工程了。xcode
(可選)移植現有的 Swift 2 應用閉包
如 果你想對一個已使用 Swift 2.0 開發的工程引入 Siri 功能,須要點擊工程,選擇 Build Settings,在 Swift Compiler - Version 下面,找到 Use Legacy Swift Language Version 選項,設置成 No。這會形成編譯器報錯,而後你能夠根據這些報錯信息來修改代碼,推薦你使用這個設置來更新代碼,以適應 Swift 不斷進化的語義。app
開始使用 SiriKitide
首先,在你的 App(或者是新建一個單視圖的 Swift 模板工程),點擊頂部的工程,而後點擊左側下方的 + 按鈕,在這裏(譯者注:我在這裏添加了一張圖片,可以說的更明白)點擊。工具
彈出的窗口中,選擇 iOS > Application Extension,接着選擇 Intents Extension。post
這 樣就給工程添加了一個新的 intent,用於監聽 Siri 的命令。其中的 Product Name 應該和你的工程文件名字類似,好比,你的 App 名爲 MusicMatcher,你能夠把這個 intent 的名字命名爲 MusicMatcherSiriIntent。必定要選中 Include UI Extension 選項,咱們以後會用到,這也是添加額外擴展的最簡單的方法。測試
我 剛剛建立的兩個新 target 能夠從項目的文件層級上找到。找到 Intent 文件夾下的 IntentHandler.swift 文件,看一下這裏面的樣本代碼。默認會提供一些示例代碼,容許用戶說一下諸如「用 MusicMatcher 開始鍛鍊」的命令,MusicMatcher 是 App 的名字。
像這樣運行示例應用
這個時候最好編譯一下代碼,而後在 iOS 真機上試一下命令。繼續,編譯應用的 target,從 Scheme 下拉菜單裏選擇 MusicMatcher,而後選擇真機,點擊 Run。
你看你會看到一個空白的應用出現,你使用的擴展這時會在後臺加載到設備的系統文件裏,如今點擊 Stop 按鈕來關閉應用。
接下來,找到你的 scheme,選擇 Intent target,點擊 Run。
這時會出現一個彈出框,問你須要鏈接哪一個應用,選擇你剛剛運行的應用:MusicMatcher。這會讓真機上再次出現這個應用(仍是一個空白的應用),不過此次調試臺(debugger)中會出現鏈接的 Intent 擴展。
如今點擊 home 按鈕回到首屏,或者應用可能本身就退出了,由於你正在運行的是 Intent,不是應用自己(這不是崩潰!!!)。
啓用擴展
擴展都已安裝就位了,可是做爲一個 iOS 用戶,仍然須要進行 Siri 設置才能使用擴展。點擊測試設備裏的 Settings,選擇 Siri 菜單,你會看到 MusicMatcher 出如今清單裏,激活容許使用 Siri。
測試咱們第一個 Siri 命令
嘗試一下 Siri 命令,長按 Home 鍵或者說出「Hey Siri」來激活 Siri(固然須要你已經激活「Hey Siri」功能)。
試一下命令,好比「使用 MusicMatcher 開始鍛鍊」。
「對不起,你須要在應用裏繼續。」
若是你像我同樣遇到了這樣的錯誤信息:「Sorry, you’ll need to continue in the app.」(不知道什麼緣由,偶爾會出現這麼一個問題,什麼鬼?)
在控制檯中你可能會看到相似的信息:
1
2
3
4
|
dyld: Library not loaded: @rpath/libswiftCoreLocation.dylib
Referenced from: /private/
var
/containers/Bundle/Application/CC815FA3-EB04-4322-B2BB-8E3F960681A0/LockScreenWidgets.app/PlugIns/JQIntentWithUI.appex/JQIntentWithUI
Reason: image not found
Program ended
with
exit code: 1
|
咱們還須要在工程裏添加 CoreLocation 庫,確保能添加到咱們編譯過的 Swift 工程中。
再 次選擇工程根目錄,選擇 MusicMatcher target。在 General 底下找到 Linked Frameworks and Libraries。點擊 + 按鈕,添加 CoreLocation.framework。如今能夠再次編譯在真機上運行,接着照着上面相同的步驟再次編譯運行 intent target。
最後,從手機桌面激活 Siri。
「Hey Siri!」
「Start my workout using MusicMatcher(使用 MusicMatcher 開始鍛鍊)」
Siri 這時候應該會迴應:「OK. exercise started on MusicMatcher(OK,開始用 MusicMatcher 鍛鍊身體)」,而後會出現一個 UI 界面寫着「Workout Started(鍛鍊開始)」。
它是如何工做的呢?
模板中的 IntentHandler 類使用了一長串的協議:
首先最主要的就是 INExtension,容許咱們一開始就把類看成一個 intent extension 來用。剩下的協議都是 intent handler 類型,在類裏可以回調:
1
2
3
4
5
|
INStartWorkoutIntentHandling
INPauseWorkoutIntentHandling
INResumeWorkoutIntentHandling
INCancelWorkoutIntentHandling
INEndWorkoutIntentHandling
|
第一個就是咱們剛剛測試過的,INStartWorkoutIntentHandling。
按住 Command 鍵點擊這些協議的名字,會看到蘋果提供的文檔:
1
2
3
4
5
|
/*!
@brief Protocol to declare support for handling an INStartWorkoutIntent
@abstract By implementing this protocol, a class can provide logic for resolving, confirming and handling the intent.
@discussion The minimum requirement for an implementing class is that it should be able to handle the intent. The resolution and confirmation methods are optional. The handling method is always called last, after resolving and confirming the intent.
*/
|
換句話說,這協議告訴 SiriKit 咱們準備處理英文句子「Start my workout with AppName Here.」
這 會根據用戶使用語言的不一樣而不一樣,不過最終的目的都是開始一次鍛鍊。INStartWorkoutIntentHandling 協議調用的幾個方法都在示例代碼裏實現了。若是你想建立一個鍛鍊應用,你能夠自行了解其餘的內容。不過在這篇教程的剩餘部分,我會添加一個新的 intent handler,來處理髮送消息。
添加一個新的消息 Intent
確認應用能夠完美運行後,讓咱們繼續,添加一個新的 intent 類型,用於發送消息,這裏的文檔說明了下列信息:
Send a message
Handler:INSendMessageIntentHandling protocol
Intent:INSendMessageIntent
Response:INSendMessageIntentResponse
在類裏添加 INSendMessageIntentHandling 協議。首先要明確,咱們把它添加到類協議清單裏,也就是在 IntentHandler.swift 文件裏。因爲實際上我不想使用這些 intent,因此我會刪除它們,只留下這一個:
1
2
|
class IntentHandler: INExtension, INSendMessageIntentHandling {
...
|
若是這時候編譯,是不會經過編譯的,由於咱們還須要實現一些遵照 INSendMessageIntentHandling 協議所必需的方法。
另外,若是你須要覈對具體是哪些方法,只須要按住 Command 鍵而後鼠標點擊 INSendMessageIntentHandling,而後看一下哪些方法前面沒有 optional 關鍵詞便可。
在這裏,咱們發現只有一個必須實現的方法:
1
2
3
4
5
6
7
8
9
|
/*!
@brief handling method
@abstract Execute the task represented by the INSendMessageIntent that's passed in
@discussion This method is called to actually execute the intent. The app must return a response for this intent.
@param sendMessageIntent The input intent
@param completion The response handling block takes a INSendMessageIntentResponse containing the details of the result of having executed the intent
@see INSendMessageIntentResponse
*/
public func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Swift.Void)
|
遵照新消息意圖協議
回到 IntentHandler.swift 文件,添加一行分隔符(藉助 jump bar,在導航查找代碼時這個分隔符會很是有用)
1
|
// MARK: - INSendMessageIntentHandling
|
在 MARK 底下,咱們來實現方法。我發現 Xcode 8 很是有用,經過敲擊方法名字的開始部分,剩下的都能交給自動補全來完成了,而後選擇對應的方法。
在 handler 裏,咱們須要建立一個 INSendMessageIntentResponse,來回調閉包。先假設全部的信息發送都很成功,在 INSendMessageIntentResponse 裏返回一個用戶活動的成功值,和默認模板中的實現很是相似。還須要添加一個 print 方法,當 handler 方法被 Siri 事件觸發後咱們就能知曉啦:
1
2
3
4
5
6
|
func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {
print(
"Message intent is being handled."
)
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent))
let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
completion(response)
}
|
把這個 intent 類型添加到 Info.plist
在具有處理 INSendMessageIntent 方法以前,咱們須要在 Info.plist 文件裏添加一些值,就看成是應用的受權吧。
在 intent 的 Info.plist 文件裏,找到並點開 NSExtension 鍵。接着點開 NSExtensionAttributes,而後是 IntentsSupported,咱們須要給 INSendMessageIntent 新添加一行,容許應用處理信息 intents。
測試新的 intent
如今咱們已經設置好了新的 intent,來測試一下。記住,你必須先編譯 App,在真機上運行,接着運行擴展進行調試,若是你不這樣作,擴展要麼不會工做,要麼不會在 Xcode 的控制檯中打印日誌。調用 Siri 的 intent,你如今能夠看到會出現一個新的信息窗口,這個窗口目前仍是空的,畢竟咱們尚未給應用編寫什麼邏輯,咱們須要實現剩下的調用,還要添加一些信息的邏輯,實現更好的用戶體驗。
This tutorial written on June 20th, 2016 using the Xcode 8 Beta 1, and is using the Swift 3.0 toolchain.
This post is a follow-up in a multi-part SiriKit tutorial. If you have not read part 1 yet, I recommend starting there.
In order to make our Siri integration more useful, we can help fill out the content of our message using a callback method from the INSendMessageIntentHandling
protocol. Investigating this protocol you can see this show up an optional methods.
resolveRecipients(forSendMessage intent:
INSendMessageIntent
, with completion: ([
INPersonResolutionResult
]) ->
Swift
.
Void
)
resolveContent(forSendMessage intent:
INSendMessageIntent
, with completion: (
INStringResolutionResult
) ->
Swift
.
Void
)
resolveGroupName(forSendMessage intent:
INSendMessageIntent
, with completion: (
INStringResolutionResult
) ->
Swift
.
Void
)
resolveServiceName(forSendMessage intent:
INSendMessageIntent
, with completion: (
INStringResolutionResult
) ->
Swift
.
Void
)
resolveSender(forSendMessage intent:
INSendMessageIntent
, with completion: (
INPersonResolutionResult
) ->
Swift
.
Void
)
|
So we can provide SiriKit with further information by implementing as many of these resolutions as we wish. Effectively enabling us to provide information regarding the recipients, content, group name, service name, or sender. These should be relatively self-explanatory.
Let’s try providing some static data for our title and content, to demonstrate how resolutions work.
First, let’s add the resolution for the content of the message, by implementing the resolveContent
protocol method.
func
resolveContent(forSendMessage intent:
INSendMessageIntent
, with completion: (
INStringResolutionResult
) ->
Void
) {
let
message =
"My message body!"
let
response =
INStringResolutionResult
.success(with: message)
completion(response)
}
|
Here we create a string resolution result, and call the success function. This is the simplest way to proceed, but we also have the option of returning a disambiguation
, confirmationRequired
, or unsupported
response. We’ll get to those later, but first let’s actually use the data Siri is providing us.
Siri will send in it’s own transcription of our message in the intent
object. We’re interested in the content
property, so let’s take that and embed it inside of a string.
func
resolveContent(forSendMessage intent:
INSendMessageIntent
, with completion: (
INStringResolutionResult
) ->
Void
) {
let
message =
"Dictated text: \(content!)"
let
response =
INStringResolutionResult
.success(with: message)
completion(response)
}
|
The content property is an optional, and as such we need to make sure Siri actually provided a transcription. If no transcription was provided then a message won’t be entirely useful, so we need to tell Siri that the information is missing and we need this value. We can do this by returning a resolution result calling the needsValue
class method on INStringResolutionResult
.
func
resolveContent(forSendMessage intent:
INSendMessageIntent
, with completion: (
INStringResolutionResult
) ->
Void
) {
if
let
content = intent.content {
let
message =
"Dictated text: \(content)"
let
response =
INStringResolutionResult
.success(with: message)
completion(response)
}
else
{
let
response =
INStringResolutionResult
.needsValue()
completion(response)
}
}
|
Now SiriKit knows when we try to send a message, that the content value is a requirement. We should implement the same type of thing for the recipients. In this case, recipients can have multiple values, and we can look them up in a variety of ways. If you have a messaging app, you would need to take the INPerson
intent object that is passed in and try to determine which of your own user’s the message is intended for.
This goes outside the scope of this Siri tutorial, so I’ll leave it up to you to implement your own application logic for the resolveRecipients
method. If you want to see an example implementation, Apple have released some sample code here.
We’ll be continuing to investigate iOS 10 and publish more free tutorials in the future. If you want to follow along be sure to subscribe to our newsletter and follow me on Twitter.
Thanks, Jameson