Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
textViewDidChangeSelection(_:)
// Exists inside of a scrollview subclass containing an "expanding" textview that lives underneath other content.
private var previousSelection: CGRect?
func textViewDidChangeSelection(_ textView: UITextView) {
guard textView.isFirstResponder else {
return
}
var selection = textView.layoutManager.boundingRect(forGlyphRange: textView.selectedRange, in: textView.textContainer).offsetBy(dx: insets.left, dy: insets.top)
let contentInset: UIEdgeInsets
if #available(iOS 11.0, *) {
contentInset = self.adjustedContentInset
}
else {
contentInset = self.contentInset
}
let scale = UIScreen.main.scale
let selectionOrigin = round(selection.origin.y / scale) * scale
let selectionHeight = round(selection.size.height / scale) * scale
let absoluteMinY = 0 - contentInset.top
let absoluteMaxY = self.contentSize.height - self.bounds.height + contentInset.bottom
let selectionMinY = selectionOrigin - textView.textContainerInset.top + (textView.frame.origin.y - contentInset.top)
let selectionMaxY = (textView.frame.origin.y + selectionOrigin + selectionHeight + textView.textContainerInset.bottom) - (self.bounds.height - contentInset.bottom)
if let previous = previousSelection {
let expanding = selection.height > previous.height
let contracting = selection.height < previous.height
let moving = !expanding && !contracting
let offsetY: CGFloat
if expanding, selection.minY < previous.minY {
offsetY = selectionMinY // up
}
else if expanding, selection.maxY > previous.maxY {
offsetY = selectionMaxY // down
}
else if contracting, selection.minY > previous.minY {
offsetY = selectionMinY // up
}
else if contracting, selection.maxY < previous.maxY {
offsetY = selectionMaxY // down
}
else if moving, selection.minY < previous.minY {
offsetY = selectionMinY // up
}
else if moving, selection.maxY > previous.maxY {
offsetY = selectionMaxY // down
}
else {
return
}
if offsetY == selectionMinY, offsetY < self.contentOffset.y {
self.contentOffset = CGPoint(x: self.contentOffset.x, y: min(max(offsetY, absoluteMinY), absoluteMaxY))
}
else if offsetY == selectionMaxY, offsetY > self.contentOffset.y {
self.contentOffset = CGPoint(x: self.contentOffset.x, y: min(max(offsetY, absoluteMinY), absoluteMaxY))
}
else if selectionMinY < self.contentOffset.y {
self.contentOffset = CGPoint(x: self.contentOffset.x, y: min(max(selectionMinY, absoluteMinY), absoluteMaxY))
}
else if selectionMaxY > self.contentOffset.y {
self.contentOffset = CGPoint(x: self.contentOffset.x, y: min(max(selectionMaxY, absoluteMinY), absoluteMaxY))
}
}
else if selectionMinY < self.contentOffset.y {
self.contentOffset = CGPoint(x: self.contentOffset.x, y: min(max(selectionMinY, absoluteMinY), absoluteMaxY))
}
else if selectionMaxY > self.contentOffset.y {
self.contentOffset = CGPoint(x: self.contentOffset.x, y: min(max(selectionMaxY, absoluteMinY), absoluteMaxY))
}
self.previousSelection = selection
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment