Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

Question about `UITextField`'s `markedTextRange` when handling Korean input

I'm currently working on implementing a character limit for Korean text input using UITextField, but I've encountered two key issues.

1. How can I determine if Korean input is complete?

I understand that markedTextRange represents provisional (composing) text during multistage text input systems (such as Korean, Japanese, Chinese).

While testing with Korean input, I expected markedTextRange to reflect the composing state. However, it seems that markedTextRange remains nil throughout the composition process.


2. Problems limiting character count for Korean input

I’ve tried two methods to enforce a character limit. Both lead to incorrect behavior due to how Korean characters are composed.

Method 1 – Before replacement:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
return text.count <= 5
}

This checks the text length before applying the replacementString. The issue is that when the user enters a character that is meant to combine with the previous one to form a composed character, the input should result in a single, combined character. However, because the character limit check is based on the state before the replacement is applied, the second character does not get composed as expected.

Method 2 – After change:

textField.addTarget(self, action: #selector(editingChanged), for: .editingChanged)

@objc private func editingChanged(_ sender: UITextField) {
guard var text = sender.text else { return }

if text.count > limitCount {
text.removeLast()
sender.text = text
}
}

This removes the last character if the count exceeds the limit after the change. But when a user keeps typing past the limit, the last character is overwritten by new input.

I suspect this happens because the .editingChanged event occurs before the multistage input is finalized, and the final composed character is applied after that event.

My understanding of the input flow:

Standard input:

  1. shouldChangeCharactersIn is called
  2. replacementString is applied
  3. .editingChanged is triggered

With multistage input (Korean, etc.):

  1. shouldChangeCharactersIn is called
  2. replacementString is applied
  3. .editingChanged is triggered
  4. Final composed character is inserted (after all the above)

Conclusion

Because both approaches lead to incorrect character count behavior with Korean input, I believe I need a new strategy.

Is there an officially recommended way to handle multistage input properly with UITextField in this context?

Any advice or clarification would be greatly appreciated.


MacOS 15.5(24F74) Xcode 16.4 (16F6)

Answered by DTS Engineer in 843098022

Ideally, if markedTextRange is well maintained, it should turn nil when a multi-stage input is complete, and you can then check the character count from there. That happens for Chinese and Japanese text input, but not for Korean, as you have noticed.

I don't know Korean, but markedTextRange being always nil for Korean text input looks like a bug to me, and I'd hence suggest that you file a feedback report – If you do so, please share your report ID here, and I'd route it directly to the relevant team.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

You should probably count for unicodeScalars.count, as described in Xcode doc about Character.

Because each character in a string can be made up of one or more Unicode scalar values, the number of characters in a string may not match the length of the Unicode scalar value representation or the length of the string in a particular binary representation.
print("Unicode scalar value count: \(greeting.unicodeScalars.count)")
// Prints "Unicode scalar value count: 8"

print("UTF-8 representation count: \(greeting.utf8.count)")
// Prints "UTF-8 representation count: 11"

So change to

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    guard let text = textField.text else { return true }
    return text.unicodeScalars.count <= 5
}
Accepted Answer

Ideally, if markedTextRange is well maintained, it should turn nil when a multi-stage input is complete, and you can then check the character count from there. That happens for Chinese and Japanese text input, but not for Korean, as you have noticed.

I don't know Korean, but markedTextRange being always nil for Korean text input looks like a bug to me, and I'd hence suggest that you file a feedback report – If you do so, please share your report ID here, and I'd route it directly to the relevant team.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Question about &#96;UITextField&#96;'s &#96;markedTextRange&#96; when handling Korean input
 
 
Q