diff --git a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift index c6e928b3964404..3504da36af422a 100644 --- a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift +++ b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift @@ -95,12 +95,6 @@ class RCTAztecView: Aztec.TextView { let placeholderWidthInset = 2 * leftTextInset return placeholderLabel.widthAnchor.constraint(equalTo: widthAnchor, constant: -placeholderWidthInset) }() - - /// If a dictation start with an empty UITextView, - /// the dictation engine refreshes the TextView with an empty string when the dictation finishes. - /// This helps to avoid propagating that unwanted empty string to RN. (Solving #606) - /// on `textViewDidChange` and `textViewDidChangeSelection` - private var isInsertingDictationResult = false // MARK: - Font @@ -355,16 +349,18 @@ class RCTAztecView: Aztec.TextView { } // MARK: - Dictation - - override func dictationRecordingDidEnd() { - isInsertingDictationResult = true - } - - public override func insertDictationResult(_ dictationResult: [UIDictationPhrase]) { - let objectPlaceholder = "\u{FFFC}" - let dictationText = dictationResult.reduce("") { $0 + $1.text } - isInsertingDictationResult = false - self.text = self.text?.replacingOccurrences(of: objectPlaceholder, with: dictationText) + + func removeUnicodeAndRestoreCursor(from textView: UITextView) { + // Capture current cursor position + let originalPosition = textView.offset(from: textView.beginningOfDocument, to: textView.selectedTextRange?.start ?? textView.beginningOfDocument) + + // Replace occurrences of the obj symbol ("\u{FFFC}") + textView.text = textView.text?.replacingOccurrences(of: "\u{FFFC}", with: "") + + if let newPosition = textView.position(from: textView.beginningOfDocument, offset: originalPosition) { + // Move the cursor to the correct, new position following dictation + textView.selectedTextRange = textView.textRange(from: newPosition, to: newPosition) + } } // MARK: - Custom Edit Intercepts @@ -771,7 +767,7 @@ class RCTAztecView: Aztec.TextView { extension RCTAztecView: UITextViewDelegate { func textViewDidChangeSelection(_ textView: UITextView) { - guard isFirstResponder, isInsertingDictationResult == false else { + guard isFirstResponder else { return } @@ -784,10 +780,13 @@ extension RCTAztecView: UITextViewDelegate { } func textViewDidChange(_ textView: UITextView) { - guard isInsertingDictationResult == false else { - return + // Workaround for RN dictation bug that adds obj symbol. + // Ref: https://github.com/facebook/react-native/issues/36521 + // TODO: Remove workaround when RN issue is fixed + if textView.text?.contains("\u{FFFC}") == true { + removeUnicodeAndRestoreCursor(from: textView) } - + propagateContentChanges() updatePlaceholderVisibility() //Necessary to send height information to JS after pasting text. diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index e575e5ebe6e06e..1ec6340bcd01fd 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -21,6 +21,8 @@ For each user feature we should also add a importance categorization label to i - [**] Fix undo/redo history when inserting a link configured to open in a new tab [#50460] - [*] [List block] Fix an issue when merging a list item into a Paragraph would remove its nested list items. [#50701] +- [**] [iOS] Fix dictation regression, in which typing/dictating at the same time caused content loss. [#49452] + ## 1.95.0 - [*] Fix crash when trying to convert to regular blocks an undefined/deleted reusable block [#50475] - [**] Tapping on nested text blocks gets focus directly instead of having to tap multiple times depeding on the nesting levels. [#50108]