Detecting marked range in UI/NSTextViews at the time of shouldChangeTextIn

We have submitted a feedback for this issue: FB21230723

We're building a note-taking app for iOS and macOS that uses both UITextView and NSTextView.

When performing text input that involves a marked range (such as Japanese input) in a UITextView or NSTextView with a UITextViewDelegate or NSTextViewDelegate set, the text view's marked range (markedTextRange / markedRange()) has not yet been updated at the moment when shouldChangeTextIn is invoked.

  • UITextViewDelegate.textView(_:shouldChangeTextIn:replacementText:)
  • NSTextViewDelegate.textView(_:shouldChangeTextIn:replacementString:)

The current behavior is this when entering text in Japanese: (same for NSTextView)

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        print(textView.markedTextRange != nil)  // prints out false
        DispatchQueue.main.async {
            print(textView.markedTextRange != nil)  // prints out true
        }
}

However, we need the value of markedTextRange right away in order to determine whether to return true or false from this method.

Is there any workaround for this issue?

Answered by DTS Engineer in 871644022

It seems that iOS does update markedTextRange for me. I tried the following code:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    if let markedTextRange = textView.markedTextRange {
        let markedText = textView.text(in: markedTextRange) ?? ""
        print("markedText = \(markedText), replacementText = \(text)")
    } else {
        print("replacementText = \(text)")
    }
    return true
}

And get the following result when inputting "あ" and then "か" with Japanese Kana input method:

replacementText = あ
markedText = あ, replacementText = か

Do you see the same thing? is markedText + replacementText not enough for you to determine whether the input is valid?

I tested with iPhone + iOS 26.1(23B85), if that matters.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

It seems that iOS does update markedTextRange for me. I tried the following code:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    if let markedTextRange = textView.markedTextRange {
        let markedText = textView.text(in: markedTextRange) ?? ""
        print("markedText = \(markedText), replacementText = \(text)")
    } else {
        print("replacementText = \(text)")
    }
    return true
}

And get the following result when inputting "あ" and then "か" with Japanese Kana input method:

replacementText = あ
markedText = あ, replacementText = か

Do you see the same thing? is markedText + replacementText not enough for you to determine whether the input is valid?

I tested with iPhone + iOS 26.1(23B85), if that matters.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Thank you for your reply.

I may not have clearly conveyed my intent, so let me clarify my question.

What I’d like to determine is whether the replacementText itself will be treated as marked text.

In your example, even though "あ" is not marked text at the moment it is entered, I’d like to know whether that input should be treated as marked text.

Thanks for the clarification. I don't think you can do so, because the input isn't committed to the text view before textView(_:shouldChangeTextInRanges:replacementText:) returns true.

Does textInputMode help? If you know that the current input mode is Japanese Kana, can you infer that the "あ" in current replacementText should be a part of the marked text?

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Thanks for the suggestion.

Unfortunately, using textInputMode to infer whether the current input will involve marked text doesn’t seem to be perfect.

For Japanese input sources, this generally works in practice, except for certain cases such as emoji input.

However, marked text can also appear even when the selected input source is English.

For example, entering accented characters can involve marked text (e.g. Option+e produces "´", then typing "a" results in "á", where "´" is marked).

In addition, the shouldChangeTextIn method is called not only during text input but also during undo operations or Writing Tools operations, where even kana characters are not marked.

For these reasons, the input source alone can’t reliably indicate whether marked text is involved.

That said, in the AppKit case, similar information can be obtained via NSTextView.inputContext?.selectedKeyboardInputSource.
Is it correct to treat this as the AppKit equivalent of textInputMode?

Yes, you can see NSTextView.inputContext?.selectedKeyboardInputSource the the AppKit equivalent of textInputMode.

I don't have the whole picture about what you'd do on the marked text, but for the cases where the input isn't committed to the text view yet, the text is still at the input method level, which I don't think is available to your app.

You might consider holding off what you would do until the next input coming in, or even after the change is committed, if that is appropriate.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Detecting marked range in UI/NSTextViews at the time of shouldChangeTextIn
 
 
Q