SiriKit测试全攻略

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/axman/article/details/61192446
需求来源:
      我的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,界面就不是你设置的这个。用蓝色可以分清楚它到底是不是在和你的应用在交互。

猜你喜欢

转载自blog.csdn.net/axman/article/details/61192446
今日推荐