I’m building a custom input field using UITextField. When the user taps to focus the field and the text is long, the entire text becomes selected by default. This is the same behavior you can see in iOS Safari’s search field or the Messages app search field.
What I want: when the field becomes first responder, the caret should be placed at the end of the text (latest word), without selecting all the text.
Here’s the code that builds my text field:
public func makeTextField() -> UITextField {
    let textField = UITextField()
    textField.autocorrectionType = .no
    textField.setContentCompressionResistancePriority(.required, for: .horizontal)
    textField.setContentCompressionResistancePriority(.required, for: .vertical)
    if #available(iOS 13.0, *) {
        textField.smartInsertDeleteType = .no
    }
    textField.smartQuotesType = .no
    textField.smartDashesType = .no
    textField.autocapitalizationType = .none
    textField.contentMode = .scaleToFill
    if let font = attributes[.font] as? UIFont {
        textField.font = font
    }
    if let color = attributes[.foregroundColor] as? UIColor {
        textField.textColor = color
    }
    // Truncate long text at the head
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.lineBreakMode = .byTruncatingHead
    textField.defaultTextAttributes[.paragraphStyle] = paragraphStyle
    textField.delegate = self
    textField.backgroundColor = .clear
    textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
    return textField
}
Entire text is selected when focusing the field if the text is long.
What I’ve tried
Forcing the caret to the end in textFieldDidBeginEditing:
func textFieldDidBeginEditing(_ textField: UITextField) {
    let end = textField.endOfDocument
    textField.selectedTextRange = textField.textRange(from: end, to: end)
}
Doing the same asynchronously (next runloop) to avoid the system overriding selection:
func textFieldDidBeginEditing(_ textField: UITextField) {
    DispatchQueue.main.async {
        let end = textField.endOfDocument
        textField.selectedTextRange = textField.textRange(from: end, to: end)
    }
}
Despite these, the system still selects all text on focus when the string is long/truncated at the head.