Background#
A small knowledge point record, the use of markedTextRange in textField. If you already know this, you don't need to read further.
Comparison of different implementation methods for iOS input box character limits:
- Method 1: By listening to the
UIControl.Event.editingChangedof the textField, perform length interception checks in the corresponding method. - Method 2: Use the textField's delegate method,
textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Boolfor judgment.
Comparison#
Assuming the product requires this input box to limit input to 6 characters, how to judge? Let's take a look.
Method 1#
Declare a custom MWCustomTF, then listen for the editingChanged event, and check if the input characters exceed the maximum input length in the event. The code is as follows:
class MWCustomTF: UITextField {
// MARK: - properties
var kMaxInputLength: Int = 6 // Maximum input length
// MARK: - init
override init(frame: CGRect) {
super.init(frame: frame)
addTarget(self, action: #selector(handleTFChanged(_:)), for: UIControl.Event.editingChanged)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - utils
// MARK: - action
@objc
fileprivate func handleTFChanged(_ sender: UITextField) {
guard let text = sender.text else {
return
}
let textCount = text.count
let minCount = min(textCount, kMaxInputLength)
self.text = (text as NSString).substring(to: minCount)
}
// MARK: - other
}
After running and debugging, it was found that it indeed limited the maximum input length, but there were two problems:
- Problem 1: When inputting Chinese, the input of pinyin cannot exceed the maximum input length. For example, if the maximum length is 6, when inputting more than 6 pinyin words, it cannot be input. For instance, trying to input Shanghai directly shows "shang" in the input box.
- Problem 2: This method causes issues on iOS 12.0 devices, where inputting pinyin directly shows the pinyin in the input box. Originally, it was supposed to input pinyin and select Chinese characters, but after adding this method, on iOS 12, the pinyin appears directly in the input box during the input process, which is completely messed up. Therefore, it is completely unusable on iOS 12.
The effect is as follows:

Method 2#
Since the above Method 1 is completely unusable on iOS 12, let's try the implementation of Method 2, which is to judge in the textField's delegate method. The code is as follows:
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
@IBOutlet var textField: UITextField! // Link this to a UITextField in Storyboard
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textFieldText = textField.text,
let rangeOfTextToReplace = Range(range, in: textFieldText) else {
return false
}
let substringToReplace = textFieldText[rangeOfTextToReplace]
let count = textFieldText.count - substringToReplace.count + string.count
return count <= 6
}
}
After running and debugging, checking the running effect, it was found that the confusion on iOS 12 was resolved; secondly, when exceeding 6 characters, it does not automatically bring pinyin into the input box, only limiting the input beyond that to be invalid.
So this method solves the previous problems, but there is still one issue: after entering 5 characters, you can only enter one more pinyin. Surprising, right?
The effect is as follows:

So the above method is also not feasible. How to handle it? I want to not validate when entering pinyin, but check the length when selecting pinyin to turn it into Chinese characters. How to do this?
At this point, we need the textField's markedTextRange, which is defined as follows:
/* If text can be selected, it can be marked. Marked text represents provisionally
* inserted text that has yet to be confirmed by the user. It requires unique visual
* treatment in its display. If there is any marked text, the selection, whether a
* caret or an extended range, always resides within.
*
* Setting marked text either replaces the existing marked text or, if none is present,
* inserts it from the current selection. */
@available(iOS 3.2, *)
var markedTextRange: UITextRange? { get } // Nil if no marked text.
Based on whether markedTextRange is nil, we can determine if we are currently inputting pinyin. So how to handle it?
Since Method 1 was incompatible with iOS 12, we prioritize adding the check for whether markedTextRange is nil in Method 2's delegate method. However, when printing textField.markedTextRange in the delegate method, it is found that the range printed here is always one step behind. That is, when inputting one pinyin, it prints nil in this method, and after inputting the second pinyin, it prints range = 0...1. Therefore, this method cannot accurately determine this value. This is actually because the method is called earlier, and after this method returns true, markedTextRange will change, so the markedTextRange seen in this method is always one step behind.
Thus, we have to continue using Method 1, because Method 1 listens to the editingChanged event of the textField, so the markedTextRange obtained in this event should be accurate. The code is as follows:
class MWCustomTF: UITextField {
// MARK: - properties
var kMaxInputLength: Int = 6
// MARK: - init
override init(frame: CGRect) {
super.init(frame: frame)
addTarget(self, action: #selector(handleEditingChanged(_:)), for: UIControl.Event.editingChanged)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - utils
// MARK: - action
@objc
fileprivate func handleEditingChanged(_ sender: UITextField) {
guard let text = sender.text else {
return
}
if sender.markedTextRange != nil {
return
}
let textCount = text.count
let minCount = min(textCount, kMaxInputLength)
self.text = (text as NSString).substring(to: minCount)
}
// MARK: - other
}
After running and debugging, it was found to be perfect. When entering pinyin, it does not check whether it exceeds the maximum length, and when selecting pinyin to turn it into Chinese characters, characters exceeding the maximum length will be truncated. Bingo, this is exactly the result we wanted.
The final effect is as follows:
