From dbcfdf8b047d5133bc95ec2166f0821d8e6dd9fc 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` ## Changelog: [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 +++++++++++++ .../Text/TextInput/RCTBackedTextInputViewProtocol.h | 1 + .../Libraries/Text/TextInput/RCTBaseTextInputView.m | 8 ++++++-- .../Text/TextInput/Singleline/RCTUITextField.h | 1 + .../Text/TextInput/Singleline/RCTUITextField.m | 13 +++++++++++++ 6 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.h b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.h index 1215ff0843402f..205f9943262add 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.h +++ b/packages/react-native/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/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.m b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.m index 5d5d3085879ba2..037a38276aee86 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.m +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.m @@ -261,6 +261,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/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index 686af9e3a363e2..a8719ecd4d0165 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/packages/react-native/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/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m index ec473bcef5df4f..10c9d30df3d01e 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m +++ b/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m @@ -119,8 +119,12 @@ - (BOOL)textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldTex } }]; - BOOL shouldFallbackToBareTextComparison = - [self.backedTextInputView.textInputMode.primaryLanguage isEqualToString:@"dictation"] || + 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; diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h index b26b41f8693d7b..91f8eb087acf87 100644 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h +++ b/packages/react-native/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/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.m b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.m index 3d116019b89cde..4d0afd97ae682a 100644 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.m +++ b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.m @@ -142,6 +142,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