I'm trying to save the location of scrolled text in a UITextView
so that I can return to that location upon loading the ViewController
again. I have very long strings, so I want the user to be able to scroll to a specific location and then return to that location later.
I'm using the UITextView
. scrollRangeToVisible function to automatically scroll the text view, but I don't know how to get the NSRange of the text that the user is seeing. Is this the best way to go about this? I tried using the setContentOffset
function but that didn't seem to do anything.
Any help is appreciated. Thanks!
I haven't tested this thoroughly but I believe the following should work. The APIs you need are documented in the UITextInput
protocol, which UITextView
adopts.
You first need to get the UITextPosition
that corresponds to a given point inside the view. You'd then convert this value into a UTF-16 character offset. For example, here I print the visible text range (in terms of UTF-16 code units) of a textView
every time the view is scrolled:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let topLeft = CGPoint(x: textView.bounds.minX, y: textView.bounds.minY)
let bottomRight = CGPoint(x: textView.bounds.maxX, y: textView.bounds.maxY)
guard let topLeftTextPosition = textView.closestPosition(to: topLeft),
let bottomRightTextPosition = textView.closestPosition(to: bottomRight)
else {
return
}
let charOffset = textView.offset(from: textView.beginningOfDocument, to: topLeftTextPosition)
let length = textView.offset(from: topLeftTextPosition, to: bottomRightTextPosition)
let visibleRange = NSRange(location: charOffset, length: length)
print("Visible range: \(visibleRange)")
}
In my tests, UITextView
tended to count lines that were barely included in the visible range (e.g. by only one pixel), so the reported visible range tended to be one or two lines larger than what a human user would say. You may have to experiment with the exact CGPoint
you pass into closesPosition(to:)
to get the results you want.