Search code examples
swiftstringemojicursor-position

swift cursor position with emojis


The standard method I use to get the cursor position in a UITextfield does not seem to work with some emojis. The following code queries a textField for cursor position after insertion of two characters, first an emoji and then a letter character. When the emoji is inserted into the textField the function returns a value of 2 for the cursor position instead of the expected result of 1. Any ideas into what I am doing wrong or how to correct this. Thanks

Here is the code from an xcode playground:

class MyViewController : UIViewController {

override func loadView() {
    //setup view
    let view = UIView()
    view.backgroundColor = .white
    let textField = UITextField()
    textField.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
    textField.textColor = .black
    view.addSubview(textField)
    self.view = view

    //check cursor position
    var str = "🐊"
    textField.insertText(str)
    print("cursor position after '\(str)' insertion is \(getCursorPosition(textField))")

    textField.text = ""
    str = "A"
    textField.insertText(str)
     print("cursor position after '\(str)' insertion is \(getCursorPosition(textField))")
}

func getCursorPosition(_ textField: UITextField) -> Int {
    if let selectedRange = textField.selectedTextRange {
        let cursorPosition = textField.offset(from: textField.beginningOfDocument, to: selectedRange.end)
        return cursorPosition
    }
    return -1
}

}

the code return the following output:

cursor position after '🐊' insertion is 2
cursor position after 'A' insertion is 1

I'm trying to use the cursor position to split the text string into two pieces -- the text that occurs before the cursor and the text that occurs after the cursor. To do this I use the cursor position as an index for a character array I have created using the map function as follows. The cursor position leads to an incorrect array index with an emoji

    var textBeforeCursor = String()
    var textAfterCursor = String()
    let array = textField.text!.map { String($0) }
    let cursorPosition = getCursorPosition(textField)
    for index in 0..<cursorPosition {
        textBeforeCursor += array[index]
    }

Solution

  • Your issue is that the NSRange value returned by UITextField selectedTextRange and the offset need to be properly converted to a Swift String.Index.

    func getCursorPosition(_ textField: UITextField) -> String.Index? {
        if let selectedRange = textField.selectedTextRange {
            let cursorPosition = textField.offset(from: textField.beginningOfDocument, to: selectedRange.end)
            let positionRange = NSRange(location: 0, length: cursorPosition)
            let stringOffset = Range(positionRange, in: textField.text!)!
    
            return stringOffset.upperBound
        }
    
        return nil
    }
    

    Once you have that String.Index you can split the string.

    if let index = getCursorPosition(textField) {
        let textBeforeCursor = textField.text![..<index]
        let textAfterCursor = textField.text![index...]
    }