Skip to content

Instantly share code, notes, and snippets.

@jellybeansoup
Created August 15, 2017 05:39
Show Gist options
  • Select an option

  • Save jellybeansoup/d24cc0f2d7240f46f5e91bf25d397c08 to your computer and use it in GitHub Desktop.

Select an option

Save jellybeansoup/d24cc0f2d7240f46f5e91bf25d397c08 to your computer and use it in GitHub Desktop.

Revisions

  1. jellybeansoup created this gist Aug 15, 2017.
    77 changes: 77 additions & 0 deletions TextViewDidChangeSelection.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,77 @@
    // 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
    }