Search code examples
iosswifttextviewscrollviewnsrange

Textview scrollRangeToVisible make range go to top


I am trying to scroll to a specific substring located in the attributedText of a UITextView. When the scrolling is complete, I want the substring to be located at the TOP of the visible textview text. However, I can only get the substring to go to the top when the textView.selectedRange is located below the range of the substring. How can I make it so the substring always appears at the top no matter where the scroll range was previously located?

This is my current code

   let text =  // a long NSAttributedString
   let substring = "put me at the top!"
   textView.attributedText = text

   func scrollToSubstring() {
      let str = text.string as NSString

      let range = str.rangeOfString(substring, options: .RegularExpressionSearch, range: NSMakeRange(0, str.length), locale: nil)

      textView.scrollRangeToVisible(range)
      // HOW CAN I MAKE IT SO range ALWAYS APPEARS AT THE TOP?
   }

Solution

  • UITextView is a UIScrollView subclass, so if you can find the substring's location in the scroll view's coordinates (possible using TextKit), then you can set the contentOffset accordingly. This is working for me:

    func scrollSubstringToTop() {
        let str = text.string as NSString
    
        let substringRange = str.rangeOfString(substring)
        let glyphRange = textView.layoutManager.glyphRangeForCharacterRange(substringRange, actualCharacterRange: nil)
        let rect = textView.layoutManager.boundingRectForGlyphRange(glyphRange, inTextContainer: textView.textContainer)
        let topTextInset = textView.textContainerInset.top
        let contentOffset = CGPoint(x: 0, y: topTextInset + rect.origin.y)
    
        textView.setContentOffset(contentOffset, animated: true)
    }