如何检测语音识别是否正在进行中

3

问题:

我有一个UITextField和一个带有发送功能的UIButton并排放置。当用户按下发送按钮时,我执行简单的操作:

- (IBAction)sendMessage: (id)sender {
   [self.chatService sendMessage: self.messageTextField.text];
   self.messageTextField.text = @""; // here I get exception
}

现在,当用户从键盘开始使用语音输入时,然后在语音输入视图(键盘)上按下“完成”并立即按下发送按钮时,我遇到了异常“范围或索引超出界限”。

可能的解决方案:

我注意到其他应用程序在语音识别服务器正在处理数据时禁用此“发送”按钮。这正好处于两个事件之间:用户按下“完成”和结果出现在文本字段中。我希望以同样的方式解决它。

我在文档中找不到可以接收此通知的位置。我找到了UITextInput协议,但这不是我需要的。

类似主题:

我尝试过什么:

  1. 简单地捕获并忽略异常。程序没有崩溃,但虚拟键盘完全无响应。
  2. [UITextInputMode currentInputMode].primaryLanguage 等于 @"dictation" 时禁用发送按钮。通知 UITextInputCurrentInputModeDidChangeNotification 在语音输入服务提交新值之前报告了听写模式的结束,此时我仍然可以点击发送按钮导致异常。我可以在 primaryLanguage 失去 @"dictation" 值时添加延迟,但我不喜欢这种方法。最有可能需要延迟的时间取决于语音识别服务的响应速度。
  3. 我已经在不同事件上添加了一堆操作(这些事件看起来像是处理:UIControlEventEditingDidBeginUIControlEventEditingChangedUIControlEventEditingDidEndUIControlEventEditingDidEndOnExit)。好消息是,看起来 UIControlEventEditingChanged 恰好在所需的时刻触发:当用户在听写视图上按下 "完成" 按钮和服务正在提交或结束听写时。因此,这是我目前最好的概念。坏消息是,这也会在其他情况下触发,并且没有信息可以区分在哪种情况下触发了此控件事件,因此我不知道应该禁用还是启用按钮或什么都不做。

你的笔记部分指出UITextInputCurrentInputModeDidChangeNotification失败,因为结束报告得太早了。如果你使用这种方法,在输入模式从“语音转文字”改变回来时,进行一个0.0延迟的performSelector来清除文本,那会怎样呢?通知在SDK中可能被阻塞了。也许你只需要延迟到下一个运行循环的轮换? - danh
是的,我已经考虑过了。我不喜欢这个解决方案(请参见编辑),但似乎这是唯一的解决方案。 - Marek R
也许你可以通过GCD将调用提交到主队列,而不是使用延迟的performSelector?这可能会在语音识别完成更新后将其放入队列中,而不会遭受投机延迟。 - DavidA
是的,如果你需要一个延迟,比如N秒,那么我同意这是在寻求麻烦,因为你会遇到真正的竞态条件,而且延迟可能会太长或者有时会导致崩溃。但是我建议使用0.0延迟,只是为了让你的消息排在队列的后面。如果这样能起作用,我认为它将可靠地工作。 - danh
就像我在编辑中写的那样,这个测试用例已经提供了这个延迟(用户必须执行两个操作,第一个操作更改“primaryLanguage”),因此零延迟不是解决方案。 - Marek R
2个回答

6

我终于找到了最终解决方案。

它既简单优雅,可以通过苹果审核,而且始终有效。只需在UIControlEventEditingChanged上做出反应,并检测替换字符的存在,就像这样:

-(void)viewDidLoad {
  [super viewDidLoad];

  [self.textField addTarget: self
                     action: @selector(eventEditingChanged:)
           forControlEvents: UIControlEventEditingChanged];
}

-(IBAction)eventEditingChanged:(UITextField *)sender {
  NSRange range = [sender.text rangeOfString: @"\uFFFC"];
  self.sendButton.enabled = range.location==NSNotFound;
}

旧的方法

最终我找到了一些解决方案。这是改进的第三个概念,混合了第二个概念(基于 这个答案)。

-(void)viewDidLoad {
  [super viewDidLoad];

  [self.textField addTarget: self
                     action: @selector(eventEditingChanged:)
           forControlEvents: UIControlEventEditingChanged];
}

-(IBAction)eventEditingChanged:(UITextField *)sender {
  NSString *primaryLanguage = [UITextInputMode currentInputMode].primaryLanguage;

  if ([primaryLanguage isEqualToString: @"dictation"]) {
    self.sendButton.enabled = NO;
  } else {
    // restore normal text field state
    self.sendButton.enabled = self.textField.text.length>0;
  }
}

- (IBAction)sendMessage: (id)sender {
   [self.chatService sendMessage: self.messageTextField.text];
   self.messageTextField.text = @"";
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
  if (self.textField.text.length==0 || !self.sendButton.enabled) {
     return NO;
   }
   [self sendMessage: textField];
   return YES;
}

// other UITextFieldDelegate methods ...

现在问题不会再出现,因为当它可能发生时用户被阻止了(准确地说,在用户按下口述视图上的“完成”按钮和语音识别服务返回结果之间)。
好消息是使用了公共API(只有@"dictation"可能是个问题,但我认为苹果应该接受它)。


你好Marek,你的最终解决方案遇到了任何问题吗?你正在生产应用程序中运行这段代码吗? - josh-fuggle
应用程序已经在应用商店上了,我没有看到任何新的错误,但是!应用程序不再受支持(客户决定推出更先进的产品),因此我没有任何新的错误报告。它应该在所有情况下都能正常工作,除非您将一些图形添加到文本中(例如表情符号),如果您这样做,则检测逻辑必须更加复杂(检测基于语音识别信号的工作进度,通过向文本临时添加图标来标记每个图标/图形)。 - Marek R
你的新方法在iOS 9中不再起作用。但是添加一个UIControlEvents.EditingChanged目标,然后检查sender.textInputMode?.primaryLanguage == "dictation"的旧方法确实有效。虽然还没有经过审核,但我认为这不会成为问题。 - Nick Yap
至少修复了iOS 8中语音识别期间修改文本可能导致崩溃的问题。这就是我需要这个解决方案的原因。 - Marek R

1
在iOS 7中,苹果推出了TextKit,因此这个问题有了新的信息: NSAttachmentCharacter = 0xfffc 用于表示附件,正如文档所述。
因此,如果您的版本大于或等于7.0,更好的方法是检查带属性字符串中的附件。

1
这不是对这个问题的答案,而更像是对我的答案的改进意见。无论如何,谢谢。 - Marek R
这些常量需要使用复杂的 API (NSCharacterSetNSRange),我更喜欢使用字符串字面量,因为它更简单。 - Marek R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接