SiriKit测试全攻略

    xiaoxiao2021-03-25  137

    需求来源:       我的iwatch上安全的楼兰宝盒,平时虽然可以用语音来打开这个app,但是打开后主界面上的按钮操作实在是蛋疼,这不仅仅是这个app的问题,iwatch操作只是面实在是小,用手指望点击很容易误按,本来想启动车了,有时却会按了开锁窗。于是想到用语音来控制 ,但是Siri只能打开主程序,无法和应用程序中功能进行交互,把这个意见提给楼兰宝盒的开发者,回复是暂时实现不了。于是我抽空对SiriKit做了一番测试。 测试过程:      关于基本SiriKit的介绍,网上有太多的介绍,www.cocoachina.com上和developer.apple.com都可以搜索到,基本原理的生命周期等不用再介绍了。在我的调试过程中可以证实,很多文章都是浅尝辄止,或者拿别人的文章改来的,有的文章中引用的代码竟然一段是objectc,一段是swift,关键点几乎没有说清楚的。      我直接写测试过程,关键点我会详细说明我测试的过程。      环境:mac os 10.12.4 Beta 4/xcode 8.3 beta4/iphone7/IOS 10.3 beta 4      因为不同版本的swift语法和类库不兼容,很多已经不存在方法需要自己用其它方法代替。 一.新建一个single view application,在info中把localiztion改成china。(很多文章中提示要改很多info.plist文件,其实默认的设置已经支持。)       在Capabilites中把Siri权限打开:       在主视图中加一个label,内容随意。编译,在真机上运行,使这个初始的app部署到真机上(希望你看到主视图上的Label内容能完全显示)。       点击TARGET左下角的+弹出新的TARGET选择窗口,选中IntentsExtension       给它起个名字同时选中IntentsUI Extension,完成。同时把localiztion都设成China       现在进入编码阶段,按照文档介绍,IntentsExtension只支持6种场景,但是实际上,只要其中的SendMessage协议,就可以完成各种功能,你把要输入的内容放在消息体中,然后在应用中解析消息体,什么样的参数完成不了呢?比如我想解决的问题就是打开车助理这个应用然后接受“启动”,“停止”,“开门窗”,“锁门窗”这些命令,你只要判断消息体中的内容对应到调用应用中的各个功能就行了。       另外SendMessage协议的recipients参数还可以帮助你准确校验命令目标,也就是说你可以确认当前Siri的交互是发给你这个应用中存在的联系人的。       关于INVocabulary关键词,为了帮助siri准确识别,把关键词放在INVocabulary中是一个好方法,特别是发送目标。为了准确判断Siri是否识别出发送目标,你应该起一个通讯录中不存在的并且发音识别度高的关键词放在INVocabulary止境中,如我为车助理这个应用内置了两个消息接受者:     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {         // Override point for customization after application launch.                  //内置联系人         let orderSet = NSMutableOrderedSet()         orderSet.add("助理小妹")         orderSet.add("助理小哥")         INVocabulary.shared().setVocabularyStrings(orderSet, of:.contactName)                  //请求Siri的访问权限         INPreferences.requestSiriAuthorization{             authorizationStatus in             switch authorizationStatus {             case .authorized:                 print("Authorized")             default:                 print("Not Authorized") //弹出窗口提醒用户去 设置->Siri->允许的程序->打开车助理的对Siri权限             }         }         return true     }                编译重新运行主程序。    对于其它事件是否要处理要看每个应用的需求,我这个应用就是要接受Siri发给应用的内容,所以在IntentsExtension说可以发送了吗?你说发送。 SiriIntentsUIExtension就会调用handle方法,你在获取语音捕获的内容就在这里处理,Apple建议是,因为你的 IntentsExtension的中调用的功能绝大多数在主程序中同样会调用,所以建议你把这段逻辑代码写在EmbeddedFramework中,当然这是代码组织的问题,在测试阶段我们不太关注。 所以我们直接在handle中实现:     func handle(sendMessage intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {         // Implement your application logic to send a message here.         var dict:[String:String] = Dictionary()         if let rs = intent.recipients, rs.count > 0{             let person = rs[0]             dict["name"] = person.displayName         }         if let text = intent.content,!text.isEmpty{             dict["message"] = text         }                  let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))         userActivity.userInfo = dict         userActivity.becomeCurrent()         print("向\(dict["name"]!)发消息:\(dict["message"]!)")         if (dict["message"]!.range(of: "测试失败") != nil) { // 利用userActivity与主程序交互数据             let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)             completion(response)         }else{             let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)             completion(response)         }     }         这里有个关键点,如果你仅在INVocabulary中添加了关键词“助手小妹”,在你说“车助理发消息给助手小妹”时,可以帮助Siri准确分析语法结构,但是如果通讯录中没有助手小妹这个联系人, intent.recipients[0]这个对象中所有字段是空的,在默认的 IntentsUIExtension交互界面上也不会显示To:的内容,如果你不介意接受人,只介意消息内容,可以不要建立一个真实的通讯录,但这样仍然有意义,Siri在分析“车助理发消息给助手小妹”时可以准确地从INVocabulary关键词中找到并知道它是一个宾语而不是内容。你可以在测试时把这个关键词从INVocabulary中去掉,试一下识别率。    真实的处理过程仅是这一句:  print("向\(dict["name"]!)发消息:\(dict["message"]!)"),你在调试时如果看到控制台显示这一行,说明它已经正确工作,这和你调用任何启动命令的行为没有区别。    调试关键点:要调用handle方法,你应该选择IntentsExtension,而不是 IntentsUIExtension,也不是主程序。才能看到print的输出,你要看哪个target的输出就要选择哪 target调试。调试主程序时,主程序在手上打开后,自己调出Siri,调 IntentsExtension和 IntentsUIExtension时,选择连接主程序。    关于 NSUserActivity,很多人不知道SiriKit中 NSUserActivity如何和主程序交互,也没有一篇文章中写到,其实 NSUserActivity的功能和Handoff中一样,只是 IntentsUI如何转到主视图的问题。 上面所有的事件中,只要你告诉 IntentsUIExtension当前处理失败,返回 .failureRequiringAppLaunch这个code, IntentsUI就会把控制权交给主程序来处理,这时 NSUserActivity就可以用来传递上下文了,上面的例子中,我专门加了一断测试逻辑,只要你发送的内容是“测试失败”,Siri就会让你打开主程序处理:

        要在主程序中接受 NSUserActivity上下文,需要在 AppDelegate中增加相应的委托函数:     //Siri发送失败会打开APP处理会调用些方法。     func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {         let alert = UIAlertController();         alert.title = "Handle userActivity"         let dict:[String:String] = userActivity.userInfo as! Dictionary         alert.message = "name: \(dict["name"]!) \r\n message: \(dict["message"]!)"         alert.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.cancel, handler: nil))         let alertWindow = UIWindow(frame: UIScreen.main.bounds)         alertWindow.rootViewController = UIViewController()         alertWindow.windowLevel = UIWindowLevelAlert + 1;         alertWindow.makeKeyAndVisible()         alertWindow.rootViewController?.present(alert, animated: true, completion: nil)         return true     }       我在这里只是把这handle中捕获的内容弹出来了: 其它的调试注意点:我为IntentsUI定制了一个界面,默认的界面是保留IntentsUIExtension中留给你的(用户界面),然后在下面绘制系统默认界面进行交互。很难看,所以我把系统默认隐藏了,只要让 IntentViewController类继承INUIHostedViewSiriProviding并实现     var displaysMessage: Bool {         return true     }这个代理方法,就可以隐藏默认系统界面,然后我在用户界面上自己加了控件显示交互内容: 自定义界面的代码:     // Prepare your view controller for the interaction to handle.     func configure(with interaction: INInteraction!, context: INUIHostedViewContext, completion: ((CGSize) -> Void)!) {         // Do configuration here, including preparing views and calculating a desired size for presentation.         let intent = interaction.intent as! INSendMessageIntent         name.text = intent.recipients?[0].displayName         messageText.text = intent.content         if let completion = completion {             completion(self.desiredSize)         }     }    把交互介面底色改成蓝色,防止在调试时误识别,如果你说“车助理发消息给助手小妹”,“车助理”三个字说早了,Siri只识别了“发消息给助手小妹”,实际上它是发短信给“助手小妹”,这时其实并不是调用你“车助理”主程序内置的Intents,界面就不是你设置的这个。用蓝色可以分清楚它到底是不是在和你的应用在交互。
    转载请注明原文地址: https://ju.6miu.com/read-18540.html

    最新回复(0)