最近公司的項目中需要加入Siri 快捷方式的功能掏颊,引入過程中遇到很多問題特此記錄。
需求
在系統(tǒng)應用“快捷指令”中添加指令驱负,無須打開原App即可調(diào)用運行功能坑质。
Flutter 部分
我的是Flutter工程,native同學可以直接跳過這部分哈
主要是使用methodChannel做通信轉發(fā)事件,安卓和iOS需要各自聲明洼滚。
以下文為例:
Future<Map> methodChannelGainShortcutsList() async {
///聲明通信頻道
const MethodChannel methodChannel =
MethodChannel('com.xxxx.xxx.xxxxxx/runner');
///聲明事件
Map result = await methodChannel.invokeMethod('gainXXXXXX');
return result;
}
iOS需要在AppDelegate中添加:
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
//meth
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "com.xxxx.xxx.xxxxxx/runner", binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if call.method == "gainXXXXXX" {
///dosthing
result(["key":""]);//return內(nèi)容根據(jù)自己需求來
}
else {
result(FlutterMethodNotImplemented)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
iOS部分
接下來開始Siri Shortcuts的項目配置重窟,第一步是添加Siri Kit Intent的配置文件。
右下角加號->File
找到SiriKit Intent陆盘,Next
右下角加號,新增一個 Intent
創(chuàng)建后是這個樣子,會自動生成一個Response
Parameter里可以做很多文章危号,可以引用多種類型,自建Enum等等
Command+B build一下素邪,intent會自動生成對應的類文件外莲。
//
// IntentIntent.swift
//
// This file was automatically generated and should not be edited.
//
#if canImport(Intents)
import Intents
@available(iOS 12.0, macOS 11.0, watchOS 5.0, *) @available(tvOS, unavailable)
@objc(IntentIntent)
public class IntentIntent: INIntent {
}
@available(iOS 12.0, macOS 11.0, watchOS 5.0, *) @available(tvOS, unavailable)
@objc(IntentIntentHandling)
public protocol IntentIntentHandling: NSObjectProtocol {
@available(*, renamed: "handle(intent:)")
@objc(handleIntent:completion:)
func handle(intent: IntentIntent, completion: @escaping (IntentIntentResponse) -> Swift.Void)
后面省略...
需要注意的是,有時Intents編譯緩慢兔朦,或者不同Xcode版本下偷线,會有未編譯的情況出現(xiàn)。
如下圖右側中沽甥,如果Custom Class的右側沒有出現(xiàn)箭頭声邦,則說明文件未編譯成功,需要多編譯嘗試幾次摆舟。
成功時亥曹,如下圖會有小箭頭邓了。
如果希望更改類名,同樣是修改這里Custom Class的值媳瞪,然后編譯骗炉。
接下來在General中添加Targets
搜索intents,并添加
記住勾選Include UI Extension幫你自動創(chuàng)建對應的UITarget材失,點擊Finish
注意
需要將前面創(chuàng)建的Intents.intentdefinition文件在新建的Targets中引入(將紅框內(nèi)勾選中)痕鳍。
這個時候編譯一下,會出現(xiàn)一個編譯循環(huán)問題:
編譯器提示:
Cycle inside Runner; building could produce unreliable results.
應該是在Flutter中才會出現(xiàn)龙巨。是由于編譯順序導致的循環(huán)依賴問題笼呆。
解決方法也很簡單
找到主工程 -> Build Phases ->調(diào)整順序
主要是下圖紅框內(nèi),錯序會出現(xiàn)循環(huán)依賴問題
親測按照本圖內(nèi)順序調(diào)整旨别,即可通過編譯
注意
如果IntentsSupported中沒有對應Intents類名诗赌,siriex會在快捷方式中找不到不到對應的處理函數(shù)。所以要確保類名已添加秸弛,如下圖铭若。
同時,這里會自動生成IntentHandler的類及.h.m文件递览,IntentHandler是所有Intents的處理函數(shù)的入口叼屠。
所以在NSExtensionPrincipalClass中的值也必須保留,更名時必須修改绞铃。
這里的INSendMessageIntent镜雨、INSearchForMessagesIntent、INSetMessageAttributeIntent儿捧,也會自動生成荚坞,如果功能用不上,對應代碼也可以刪除(本例用不到故刪除)
清理干凈后IntentHandler.m文件如下:
//
// IntentHandler.m
//
#import "IntentHandler.h"
#import "XXHandler.h"
#import "XXXHander.h"
@interface IntentHandler ()
@end
@implementation IntentHandler
- (id)handlerForIntent:(INIntent *)intent {
if([intent isKindOfClass:XXIntent.class]){
return [[XXIntentHandler alloc]init];
}
if([intent isKindOfClass:XXXIntent.class]){
return [[XXXIntentHander alloc]init];
}
return nil;
}
在handlerForIntent中進行類型分發(fā)
而在對應的XXXIntentHandler中菲盾,則需要重寫handleXXX與confirmXXX兩個函數(shù)颓影,這里方法名是自動生成的。
//
// XXXIntentHandler.m
//
#import "XXXIntentHandler.h"
#import "XXXIntent.h"
@interface XXXIntentHandler()<XXXIntentHandling>
@end
@implementation XXXIntentHandler
- (void)handleXXX:(XXXIntent *)intent completion:(void (^)(XXXIntentResponse * _Nonnull))completion{
completion( [[XXXIntentResponse alloc] initWithCode:XXXIntentResponseCodeSuccess
userActivity:nil]);
}
- (void)confirmXXX:(XXXIntent *)intent completion:(void (^)(XXXIntentResponse * _Nonnull))completion{
completion([[XXXIntentResponse alloc] initWithCode:XXXIntentResponseCodeSuccess
userActivity:nil]);
}
@end
到此就創(chuàng)建完成了懒鉴,可以進行業(yè)務方面的工作诡挂。
快捷方式的UI交互需要在siriui中自定義。
其他問題
Bundle Identifier
你的主工程和siriex临谱、siriexUI的Bundle Identifier必須符合關聯(lián)遞進關系咆畏。
舉個例子:
主工程: com.org.projectname
siriex: com.org.projectname.siriex
siriexui: com.org.projectname.siriexui
如果是修改了Bundle Identifier或者調(diào)試工程的時候需要用其他Bundle Identifier也需要修改對應是siriex、siriexui的Bundle Identifier吴裤。(App Group同理)
<NSUserActivity> has an interaction attached but it is not handled
在過去的iOS版本中是會有報錯提示旧找,在更高版本中某些情況也會有這個報錯。
在最初通常是由于找不到handlerForIntent對應實現(xiàn)麦牺。
解決方法也很簡單钮蛛,只要在handlerForIntent中return了對應類并實現(xiàn)了handleXXX方法就行鞭缭。
為什么我的Siri Shortcuts 直接打開了應用程序?不在外部執(zhí)行魏颓?
情況一:
在某些情況下岭辣,會造成這個問題,同樣也是游這個NSUserActivity類造成的錯誤甸饱。比如使用了Flutter第三方庫 flutter_siri_shortcuts和 siri_shortcuts沦童。
這兩個庫提供的方法,是基于以NSUserActivity實現(xiàn)的叹话,所以他們實現(xiàn)的功能是通過siri shortcuts 快速打開應用內(nèi)部實現(xiàn)偷遗,所以使用了這些庫會有這個情況出現(xiàn)。
情況二:
通常在業(yè)務邏輯中我們需要創(chuàng)建INShortcut來
INShortcut *shortcut = [[INShortcut alloc] initWithIntent:intent];
INUIAddVoiceShortcutViewController *voiceShortcutVC = [[INUIAddVoiceShortcutViewController alloc] initWithShortcut:shortcut];
voiceShortcutVC.delegate = self;
[self presentViewController:voiceShortcutVC animated:YES completion:nil];
這里一定要使用initWithIntent方法創(chuàng)建驼壶,如果使用了initWithUserActivity方法創(chuàng)建則會出現(xiàn)這個問題氏豌。
在底層中,Intent热凹、UserActivity應該是繼承關系泵喘,如果intent沒有實現(xiàn)會自動進入UserActivity相關的邏輯。
情況三:
由于快捷指令在數(shù)據(jù)安全般妙、高效纪铺、擴展性強個方面的優(yōu)勢。Apple在Siri方面對于Shortcuts功能也進行了多次迭代與擴展碟渺,導致了Siri Shortcuts也有著不同版本的支持鲜锚。
在Xcode15.3的版本中添加intents會默認Minimum Deployments為最高版本17.4(真是讓人相當無語)
當系統(tǒng)版本低于Minimum Deployments運行該快捷指令時,也會出現(xiàn)直接跳轉的問題止状。
這種情況烹棉,很容易在外部分享的快捷指令中出現(xiàn)攒霹、在老項目新增Intents擴展的過程中出現(xiàn)怯疤、在已有intents擴展但是Xcode升級后出現(xiàn)。