2

How to convert character index from layoutManager to String scale in swift? this is the code I'm using:

let touchPoint: CGPoint = gesture.locationOfTouch(0, inView: self.definitionLabel)
let index = layoutManager.characterIndexForPoint(touchPoint, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

please don't tell me to use advanceBy() function on the first index of the string characterset since characters like ò count two in the scale of layoutManager but swift string counts theme once.

alizx
  • 1,148
  • 2
  • 15
  • 27

2 Answers2

4

The index returned from the NSLayoutManager is "NSString based", i.e. it is the number of UTF-16 code units from the start of the string to the character at the given point. (So ò actually counts as one, but Emojis count two and flags even count four.)

To convert that index to a valid Swift String index, you can use the same approach as in https://stackoverflow.com/a/30404532/1187415:

let text = ... // the stored text
let i16 = text.utf16.startIndex.advancedBy(index, limit: text.utf16.endIndex)
// i16 is a String.UTF16View.Index
if let strIndex = String.Index(i16, within: text) {
    // strIndex is a String.CharacterView.Index
    let char = text[strIndex]
    print(char)
} 
Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
0

Updated for Swift 5

Martin R's answer gave me the rough outline; here's working Swift 5 version. Mine is for a UITextView with Attributed Text but should work just as well with regular String and for UITextField and UILabel.

    func handleTap(_ sender: UIGestureRecognizer) {
        guard let textView = sender.view as? UITextView else { return }

        guard let plaintext = textView.attributedText?.string else { return }
        //guard let plaintext = textView.text else { return }

        let location = sender.location(in: textView)
        let charIndex = textView.layoutManager.characterIndex(for: location, in: textView.textContainer,
                                                          fractionOfDistanceBetweenInsertionPoints: nil)

        if let strIndex = plaintext.utf16.index(plaintext.utf16.startIndex, offsetBy: charIndex, limitedBy: plaintext.utf16.endIndex) {
            let char = plaintext[strIndex]
            print("Character tapped was \(char)")
        }
    }

    let textTap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
    textView.addGestureRecognizer(textTap)

Eli Burke
  • 2,729
  • 27
  • 25