Ensure the cursor remains visible when composing posts

This commit is contained in:
Shadowfacts 2020-10-24 15:46:24 -04:00
parent b8f169d0cd
commit 16b02edf87
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
1 changed files with 49 additions and 0 deletions

View File

@ -176,6 +176,7 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
var text: Binding<String>
var didChange: (UITextView) -> Void
var uiState: ComposeUIState
private var caretScrollPositionAnimator: UIViewPropertyAnimator?
init(text: Binding<String>, uiState: ComposeUIState, didChange: @escaping (UITextView) -> Void) {
self.text = text
@ -186,6 +187,54 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
func textViewDidChange(_ textView: UITextView) {
text.wrappedValue = textView.text
didChange(textView)
ensureCursorVisible()
}
private func ensureCursorVisible() {
guard let textView = textView,
textView.isFirstResponder,
let range = textView.selectedTextRange,
let scrollView = findParentScrollView() else {
return
}
// We use a UIViewProperty animator to change the scroll view position so that we can store the currently
// running one on the Coordinator. This allows us to cancel the running one, preventing multiple animations
// from attempting to change the scroll view offset simultaneously, causing it to jitter around. This can
// happen if the user is pressing return and quickly creating many new lines.
if let existing = caretScrollPositionAnimator {
existing.stopAnimation(true)
}
let cursorRect = textView.caretRect(for: range.start)
var rectToMakeVisible = textView.convert(cursorRect, to: scrollView)
// move Y position of the rect that will be made visible down by the cursor's height so that there's
// some space between the bottom of the line of text being edited and the top of the keyboard
rectToMakeVisible.origin.y += cursorRect.height
let animator = UIViewPropertyAnimator(duration: 0.1, curve: .linear) {
scrollView.scrollRectToVisible(rectToMakeVisible, animated: false)
}
self.caretScrollPositionAnimator = animator
animator.startAnimation()
}
private func findParentScrollView() -> UIScrollView? {
guard let textView = textView else { return nil }
var current: UIView = textView
while let superview = current.superview {
if let scrollView = superview as? UIScrollView {
return scrollView
} else {
current = superview
}
}
return nil
}
@objc func formatButtonPressed(_ sender: UIBarButtonItem) {