我有一个 Flutter 应用程序,我使用 GetX 进行状态管理。在项目中,我想集成Siri。当我对 Siri 说“打开作业 MyApp”时,我希望应用程序打开并导航到作业页面。为了实现这一点,我在 Apple 方面使用了意图和快捷方式,并尝试使用 MethodChannel 来处理导航。
当我按照流程进行调试时,场景是这样展开的:我打开 Siri 并说出这句话,我可以跟踪我设置的打印和断点,并且我看到如下所示的日志。但是,该应用程序不会立即打开和导航。但是当我手动打开应用程序时,导航就会开始。
我想要实现的是,一旦说出该短语,应用程序就会打开并导航到相关页面。我包含了我在 Flutter 端编写的意图、快捷方式、应用程序委托和 MethodChannel 代码。
应用程序委托:
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
private let navigationChannel = "com.example.myapp/navigation"
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
setupSiriShortcuts(application: application)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func setupSiriShortcuts(application: UIApplication) {
let activity = NSUserActivity(activityType: "com.example.myapp.openhomework")
activity.title = "Open Homework Page"
activity.isEligibleForSearch = true
activity.isEligibleForPrediction = true
activity.isEligibleForPublicIndexing = true
activity.persistentIdentifier = NSUserActivityPersistentIdentifier("com.example.myapp.openhomework")
activity.userInfo = ["intentName": "openHomeworkPage"]
self.userActivity = activity
application.userActivity = activity
}
override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if userActivity.activityType == "com.example.myapp.openhomework",
let viewController = window.rootViewController as? FlutterViewController {
let navigationChannel = FlutterMethodChannel(name: self.navigationChannel, binaryMessenger: viewController.binaryMessenger)
application.windows.first?.makeKeyAndVisible()
navigationChannel.invokeMethod("openHomeworkPage", arguments: nil)
}
return true
}
}
意图:
import Foundation
import AppIntents
import Flutter
@available(iOS 16.0, *)
struct OpenHomeworkPageIntent: AppIntent {
static let title: LocalizedStringResource = "Open Homework MyApp"
func perform() async throws -> some IntentResult & ProvidesDialog {
print("Perform function started.")
guard let windowScene = await UIApplication.shared.connectedScenes.first as? UIWindowScene,
let viewController = await windowScene.windows.first?.rootViewController as? FlutterViewController else {
return .result(dialog: "Error: App not started.")
}
let navigationChannel = await FlutterMethodChannel(name: "com.example.myapp.openhomework", binaryMessenger: viewController.binaryMessenger)
// Dispatch the invokeMethod call on the main thread
DispatchQueue.main.async {
print("Invoking openHomeworkPage method.")
navigationChannel.invokeMethod("openHomeworkPage", arguments: nil)
}
print("Perform function completed.")
return .result(dialog: "Homework page open.")
}
}
快捷键:
import Foundation
import AppIntents
@available(iOS 16.0, *)
struct OpenHomeworkPageShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: OpenHomeworkPageIntent(),
phrases: [
"Open homework \(.applicationName)"
],
shortTitle: "Homework open"
)
}
}
flutter 中的方法通道:
static const platform = MethodChannel('com.example.myapp.openhomework');
@override
void initState() {
platform.setMethodCallHandler(_handleMethodCall);
}
Future<void> _handleMethodCall(MethodCall call) async {
print("methodChannel: ${call.method} ${call.arguments}");
switch (call.method) {
case 'openHomeworkPage':
try {
print("Navigating to HomeworkScreen...");
Get.offNamed(AppNavigation.HOMEWORK);
} catch (e) {
print("Error navigating to HomeworkScreen: $e");
}
break;
default:
print('Unknown method: ${call.method}');
}
}
调试日志:
-[WFBackgroundShortcutRunner runWorkflowWithDescriptor:request:inEnvironment:runningContext:completion:] Disabling privacy prompts because this is an on-the-fly shortcut.
Starting shortcut run: <WFWorkflow: 0xa660a9180, name: Open homework Metodbox , actions: 1>, request: <WFSiriWorkflowRunRequest: 0xa66114780, runSource: siri, input: no, presentationMode: Siri, output behavior: Ignore, automationType: (null), allowsHandoff: no, allowsDialogNotifications: no>
Running action <WFLinkAction: 0x102a7caf0, identifier: com.bkmobil.metodboxxx.OpenHomeworkPageIntent>
Action finished running <WFLinkAction: 0x102a7caf0, identifier: com.bkmobil.metodboxxx.OpenHomeworkPageIntent>.
Shortcut <private> has finished running with output: (null).
-[WFBackgroundShortcutRunner callWorkflowRunningCompletionBlockWithResult:] Workflow Did Finish: Calling Completion Block
-[WFBackgroundShortcutRunner listener:shouldAcceptNewConnection:]_block_invoke XPC connection invalidated
-[WFBackgroundShortcutRunnerStateMachine invalidateWithReason:] connection invalidated/interrupted while finishing shortcut or exiting runner. Exiting should already be in process, not transitioning.
-[WFBackgroundShortcutRunner unaliveProcess]_block_invoke_2 Exiting process
flutter: methodChannel: openHomeworkPage null
flutter: Navigating to HomeworkScreen...
[GETX] REPLACE ROUTE /main
[GETX] NEW ROUTE /splash
我尝试了deeplink而不是methodChannel,我尝试直接用appIntents来做,但我最终决定不走捷径。我想要实现的是,一旦说出该短语,应用程序就会打开并导航到相关页面。
您尝试通过快捷方式访问的 Flutter 引擎仅在应用程序启动时生成。默认情况下,快捷方式不会启动应用程序。可以添加
static var openAppWhenRun: Bool = true
添加到您的快捷方式,这将确保您的应用程序使用快捷方式启动,但即便如此,Flutter 引擎也需要一些时间来设置才能从 Swift 接收消息。
我建议使用像intelligence这样的插件来为您处理快捷方式的复杂性。