1

I would like a way to programmatically select just the text in the bounds and have that change as the window scrolls or changes bounds.

SelectAll will not work. I don't want the whole document. My goal is to react to whatever text scrolls into view in the window, scan it for key words and present contextual information in a second window.

swiftBoy
  • 35,607
  • 26
  • 136
  • 135
johnrubythecat
  • 1,003
  • 1
  • 13
  • 31

1 Answers1

0

I came up with a solution that, admittedly, is a bit of a hack. It estimates the start and end index of the visible text using the content offset, content height and content frame height of the textView. The answer is only an estimate because word wraps are unpredictable. The result is usually ±10 characters of the actual visible text. You could compensate for this by adding/subtracting a buffer of several characters to the start/end offsets, which would ensure that you have a substring of your textView text which definitely contains the visible text, with only a few extra characters at the beginning and end.

I hope this answer helps, or at least inspires you (or someone else) to come up with a solution that solves your exact needs.

class ViewController: UIViewController, UIScrollViewDelegate {

    @IBOutlet weak var textView: UITextView!

    let textViewText = "Here's to the crazy ones. The misfits. The rebels. The trouble-makers. The round pegs in the square holes. The ones who see things differently. They're not fond of rules, and they have no respect for the status-quo. You can quote them, disagree with them, glorify, or vilify them. But the only thing you can't do is ignore them. Because they change things. They push the human race forward. And while some may see them as the crazy ones, we see genius. Because the people who are crazy enough to think they can change the world, are the ones who do."

    override func viewDidLoad() {
        super.viewDidLoad()
        textView.text = textViewText
    }

    func scrollViewDidScroll(scrollView: UIScrollView) {

        let textViewContentHeight = Double(textView.contentSize.height)
        let textViewFrameHeight = Double(textView.frame.size.height)
        let textViewContentOffset = Double(textView.contentOffset.y)
        let textViewCharacterCount = textViewText.characters.count

        let startOffset = Int((textViewContentOffset / textViewContentHeight) * Double(textViewCharacterCount))

        // If the user scrolls quickly to the bottom so that the text is completely off the screen, we don't want to proceed
        if startOffset < textViewCharacterCount {

            let endIndex = Int(((textViewContentOffset + textViewFrameHeight) / textViewContentHeight) * Double(textViewCharacterCount))
            var endOffset = endIndex - textViewCharacterCount

            if endIndex > textViewCharacterCount {
                endOffset = 0
            }

            let visibleString = textViewText.substringWithRange(textViewText.startIndex.advancedBy(startOffset)..<textViewText.endIndex.advancedBy(endOffset))

            print(visibleString)
        }
    }
}
Austin Wood
  • 368
  • 1
  • 4
  • 14
  • I will try it immediately. Also: I think something like this is another answer (NSRange)glyphRangeForBoundingRectWithoutAdditionalLayout:(CGRect)bounds inTextContainer:(NSTextContainer *)container; then use the glyph index to access the range in the character index I'll continue work on this and post if I succeed. Thank you for the above. – johnrubythecat Sep 16 '16 at 14:39
  • here is a prior post of some interest. http://stackoverflow.com/questions/16675997/using-nsglyph-and-memory-allocation – johnrubythecat Sep 16 '16 at 15:05