From dd6f0459f4373ffff87b8e2fe6e54aa7a99b4a57 Mon Sep 17 00:00:00 2001 From: hublot Date: Tue, 23 May 2023 07:34:27 -0700 Subject: [PATCH] Adapt iOS16+ dictation (#37188) Summary: https://github.com/facebook/react-native/pull/19687 https://developer.apple.com/forums/thread/711413 When system version is lower than `iOS 16`, it does not support `dictation` and `keyboard` working at the same time, so if we modify the text, the system will immediately interrupt the `dictation`, so we need to prohibit modification of the text during `recording` and `recognition` When system version is higher than `iOS 16`, `dictation` and `keyboard` can work at the same time, so `textInputMode.primaryLanguage` is no longer changed to `dictation`, so we can modify the text during `recording`, because the system will not interrupt, but we cannot modify the text during `recognition`, Because the system will temporarily add a `_UITextPlaceholderAttachment` to display the recognition `UIActivityIndicator` [IOS][FIXED] - Adapt iOS16+ dictation judge condition Pull Request resolved: https://github.com/facebook/react-native/pull/37188 Test Plan: Test Code ```javascript constructor(props) { super(props) this.state = { value: '', logList: [], } } render() { return ( { let logList = this.state.logList logList.push(value.length <= 0 ? 'null' : value.replace(/\uFFFC/g, '_uFFFC')) this.setState({ value, logList }) }} onEndEditing={() => this.setState({ value: '', logList: [] })} /> ( {item} )} /> ) } ``` Case A: Required < iOS16 1. ensure that https://github.com/facebook/react-native/issues/18890 can work well and dictation will not be interrupted immediately https://github.com/facebook/react-native/assets/20135674/e69a609c-2dc4-48fc-8186-f9e5af3ac879 Case B: Required >= iOS16 1. ensure that https://github.com/facebook/react-native/issues/18890 can work well and dictation will not be interrupted immediately https://github.com/facebook/react-native/assets/20135674/caa97e18-c7c4-4a08-9872-b50130f73bf4 Case C: Required >= iOS16 1. start dictation 3. then do not speak any words 4. then end dictation 5. verify that `onChangeText` will callback "\uFFFC" once 6. and then verify `onChangeText` callback an empty string "" once https://user-images.githubusercontent.com/20135674/235960378-90155ec5-a129-47bc-825b-ee6cb03e7286.MP4 Case D: Required >= iOS16 1. start dictation 3. input some text while speaking some words 4. then end dictation 5. and verify that the `onChangeText` callback work fine. https://user-images.githubusercontent.com/20135674/235960411-e479d9ab-856a-4407-a644-986426825133.MP4 Case E: Required >= iOS16 1. start dictation 2. say a word 3. and then switch the keyboard to other language 4. verify that dictation will not end 6. continue say some word 8. verify the `onChangeText` callback work fine. https://user-images.githubusercontent.com/20135674/235960450-351f1aaf-80c0-4d1c-b5c9-3e2cd7225875.MP4 Reviewed By: sammy-SC Differential Revision: D45563187 Pulled By: dmytrorykun fbshipit-source-id: 7467b313769896140434f60dcb3590d0b3c1aa15 --- .../Text/TextInput/Multiline/RCTUITextView.h | 1 + .../Text/TextInput/Multiline/RCTUITextView.m | 13 +++++++++++++ .../TextInput/RCTBackedTextInputViewProtocol.h | 1 + Libraries/Text/TextInput/RCTBaseTextInputView.m | 15 +++++++++------ .../Text/TextInput/Singleline/RCTUITextField.h | 1 + .../Text/TextInput/Singleline/RCTUITextField.m | 13 +++++++++++++ 6 files changed, 38 insertions(+), 6 deletions(-) diff --git a/Libraries/Text/TextInput/Multiline/RCTUITextView.h b/Libraries/Text/TextInput/Multiline/RCTUITextView.h index 5ccb6b6d7d3fc6..c527c1e5a2e654 100644 --- a/Libraries/Text/TextInput/Multiline/RCTUITextView.h +++ b/Libraries/Text/TextInput/Multiline/RCTUITextView.h @@ -24,6 +24,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL contextMenuHidden; @property (nonatomic, assign, readonly) BOOL textWasPasted; +@property (nonatomic, assign, readonly) BOOL dictationRecognizing; @property (nonatomic, copy, nullable) NSString *placeholder; @property (nonatomic, strong, nullable) UIColor *placeholderColor; diff --git a/Libraries/Text/TextInput/Multiline/RCTUITextView.m b/Libraries/Text/TextInput/Multiline/RCTUITextView.m index 9d38dd14bc50b9..b69154d0c60b2f 100644 --- a/Libraries/Text/TextInput/Multiline/RCTUITextView.m +++ b/Libraries/Text/TextInput/Multiline/RCTUITextView.m @@ -257,6 +257,19 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender return [super canPerformAction:action withSender:sender]; } +#pragma mark - Dictation + +- (void)dictationRecordingDidEnd +{ + _dictationRecognizing = YES; +} + +- (void)removeDictationResultPlaceholder:(id)placeholder willInsertResult:(BOOL)willInsertResult +{ + [super removeDictationResultPlaceholder:placeholder willInsertResult:willInsertResult]; + _dictationRecognizing = NO; +} + #pragma mark - Placeholder - (void)_invalidatePlaceholderVisibility diff --git a/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index c235907d796a15..36f778f20ef74f 100644 --- a/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, copy, nullable) NSString *placeholder; @property (nonatomic, strong, nullable) UIColor *placeholderColor; @property (nonatomic, assign, readonly) BOOL textWasPasted; +@property (nonatomic, assign, readonly) BOOL dictationRecognizing; @property (nonatomic, assign) UIEdgeInsets textContainerInset; @property (nonatomic, strong, nullable) UIView *inputAccessoryView; @property (nonatomic, strong, nullable) UIView *inputView; diff --git a/Libraries/Text/TextInput/RCTBaseTextInputView.m b/Libraries/Text/TextInput/RCTBaseTextInputView.m index def2a04d5c5809..131193e117ca10 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputView.m +++ b/Libraries/Text/TextInput/RCTBaseTextInputView.m @@ -115,12 +115,15 @@ - (BOOL)textOf:(NSAttributedString*)newText equals:(NSAttributedString*)oldText{ } }]; - BOOL shouldFallbackToBareTextComparison = - [self.backedTextInputView.textInputMode.primaryLanguage isEqualToString:@"dictation"] || - [self.backedTextInputView.textInputMode.primaryLanguage isEqualToString:@"ko-KR"] || - self.backedTextInputView.markedTextRange || - self.backedTextInputView.isSecureTextEntry || - fontHasBeenUpdatedBySystem; + BOOL shouldFallbackDictation = [self.backedTextInputView.textInputMode.primaryLanguage isEqualToString:@"dictation"]; + if (@available(iOS 16.0, *)) { + shouldFallbackDictation = self.backedTextInputView.dictationRecognizing; + } + + BOOL shouldFallbackToBareTextComparison = shouldFallbackDictation || + [self.backedTextInputView.textInputMode.primaryLanguage isEqualToString:@"ko-KR"] || + self.backedTextInputView.markedTextRange || self.backedTextInputView.isSecureTextEntry || + fontHasBeenUpdatedBySystem; if (shouldFallbackToBareTextComparison) { return ([newText.string isEqualToString:oldText.string]); diff --git a/Libraries/Text/TextInput/Singleline/RCTUITextField.h b/Libraries/Text/TextInput/Singleline/RCTUITextField.h index 358e137f132669..74e96e81845c26 100644 --- a/Libraries/Text/TextInput/Singleline/RCTUITextField.h +++ b/Libraries/Text/TextInput/Singleline/RCTUITextField.h @@ -24,6 +24,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL caretHidden; @property (nonatomic, assign) BOOL contextMenuHidden; @property (nonatomic, assign, readonly) BOOL textWasPasted; +@property (nonatomic, assign, readonly) BOOL dictationRecognizing; @property (nonatomic, strong, nullable) UIColor *placeholderColor; @property (nonatomic, assign) UIEdgeInsets textContainerInset; @property (nonatomic, assign, getter=isEditable) BOOL editable; diff --git a/Libraries/Text/TextInput/Singleline/RCTUITextField.m b/Libraries/Text/TextInput/Singleline/RCTUITextField.m index 42a6bc801bc8a9..62d7ae1bc61b38 100644 --- a/Libraries/Text/TextInput/Singleline/RCTUITextField.m +++ b/Libraries/Text/TextInput/Singleline/RCTUITextField.m @@ -140,6 +140,19 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender return [super canPerformAction:action withSender:sender]; } +#pragma mark - Dictation + +- (void)dictationRecordingDidEnd +{ + _dictationRecognizing = YES; +} + +- (void)removeDictationResultPlaceholder:(id)placeholder willInsertResult:(BOOL)willInsertResult +{ + [super removeDictationResultPlaceholder:placeholder willInsertResult:willInsertResult]; + _dictationRecognizing = NO; +} + #pragma mark - Caret Manipulation - (CGRect)caretRectForPosition:(UITextPosition *)position