在 UITextView 中滚动后获取可见文本的 NSRange
Get the NSRange for the visible text after scroll in UITextView
我正在尝试将滚动文本的位置保存在 UITextView
中,以便在再次加载 ViewController
时可以 return 到该位置。我有很长的字符串,所以我希望用户能够滚动到特定位置,然后 return 稍后滚动到该位置。
我正在使用 UITextView
。 scrollRangeToVisible 函数自动滚动文本视图,但我不知道如何获取用户看到的文本的 NSRange。这是最好的方法吗?我尝试使用 setContentOffset
函数,但似乎没有任何效果。
感谢任何帮助。谢谢!
我还没有对此进行彻底的测试,但我相信以下应该有效。您需要的 API 记录在 UITextInput
协议中,UITextView
采用该协议。
您首先需要获取对应于视图内给定点的UITextPosition
。然后将此值转换为 UTF-16 字符偏移量。例如,我在这里每次滚动视图时打印 textView
的可见文本范围(以 UTF-16 代码单元表示):
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)")
}
在我的测试中,UITextView
倾向于计算几乎不包含在可见范围内的行(例如,仅一个像素),因此报告的可见范围往往比所显示的大一或两行人类用户会说。您可能需要对传递给 closesPosition(to:)
的确切 CGPoint
进行试验,以获得您想要的结果。
这里有一个 UITextView
的小扩展,它使用 characterRange(at:)
函数来代替。它还将计算的 属性 添加到 return 当前可见的文本:
extension UITextView {
private var firstVisibleCharacterPosition: UITextPosition? {
// ⚠️ For some reason `characterRange(at:)` returns nil for points with a low y value.
guard let scrolledPosition = characterRange(at: contentOffset)?.start else {
return beginningOfDocument
}
return scrolledPosition
}
private var lastVisibleCharacterPosition: UITextPosition? {
return characterRange(at: bounds.max)?.end
}
/// The range of the text that is currently displayed within the text view's bounds.
var visibleTextRange: UITextRange? {
guard
let first = firstVisibleCharacterPosition,
let last = lastVisibleCharacterPosition else {
return nil
}
return textRange(from: first, to: last)
}
/// The string that is currently displayed within the text view's bounds.
var visibleText: String? {
guard let visibleTextRange = visibleTextRange else {
return nil
}
return text(in: visibleTextRange)
}
}
我在上面的代码中使用了这些 shorthand 属性:
extension CGRect {
var min: CGPoint {
return .init(x: minX, y: minY)
}
var max: CGPoint {
return .init(x: maxX, y: maxY)
}
}
我正在尝试将滚动文本的位置保存在 UITextView
中,以便在再次加载 ViewController
时可以 return 到该位置。我有很长的字符串,所以我希望用户能够滚动到特定位置,然后 return 稍后滚动到该位置。
我正在使用 UITextView
。 scrollRangeToVisible 函数自动滚动文本视图,但我不知道如何获取用户看到的文本的 NSRange。这是最好的方法吗?我尝试使用 setContentOffset
函数,但似乎没有任何效果。
感谢任何帮助。谢谢!
我还没有对此进行彻底的测试,但我相信以下应该有效。您需要的 API 记录在 UITextInput
协议中,UITextView
采用该协议。
您首先需要获取对应于视图内给定点的UITextPosition
。然后将此值转换为 UTF-16 字符偏移量。例如,我在这里每次滚动视图时打印 textView
的可见文本范围(以 UTF-16 代码单元表示):
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)")
}
在我的测试中,UITextView
倾向于计算几乎不包含在可见范围内的行(例如,仅一个像素),因此报告的可见范围往往比所显示的大一或两行人类用户会说。您可能需要对传递给 closesPosition(to:)
的确切 CGPoint
进行试验,以获得您想要的结果。
这里有一个 UITextView
的小扩展,它使用 characterRange(at:)
函数来代替。它还将计算的 属性 添加到 return 当前可见的文本:
extension UITextView {
private var firstVisibleCharacterPosition: UITextPosition? {
// ⚠️ For some reason `characterRange(at:)` returns nil for points with a low y value.
guard let scrolledPosition = characterRange(at: contentOffset)?.start else {
return beginningOfDocument
}
return scrolledPosition
}
private var lastVisibleCharacterPosition: UITextPosition? {
return characterRange(at: bounds.max)?.end
}
/// The range of the text that is currently displayed within the text view's bounds.
var visibleTextRange: UITextRange? {
guard
let first = firstVisibleCharacterPosition,
let last = lastVisibleCharacterPosition else {
return nil
}
return textRange(from: first, to: last)
}
/// The string that is currently displayed within the text view's bounds.
var visibleText: String? {
guard let visibleTextRange = visibleTextRange else {
return nil
}
return text(in: visibleTextRange)
}
}
我在上面的代码中使用了这些 shorthand 属性:
extension CGRect {
var min: CGPoint {
return .init(x: minX, y: minY)
}
var max: CGPoint {
return .init(x: maxX, y: maxY)
}
}