2

I'm trying to force UITextView to keep caret always on the same fixed height, for example in the 1/4 of screen.

I should behave similar to old typewriters - when user presses enter (or reaches end of line) text should scroll one line up and caret should stay in the same y position and jump to the begining of new line.

I was trying to do it like so, but it behaves unexpectedly, caret jumps randomly sometimes and scrolling is visible, it scrolls itself down and then I scroll it up again with scrollRectToVisible, this do not seem like ideal way of doing it.

How can I achieve such effect? Any library or pod with similar functionality would also be much appreciated.

func setScrollToMiddle() {
    if let selectedRange = textView.selectedTextRange {

        let caretRect = textView.caretRect(for: selectedRange.start)            
        let middleOfCaretHeight = caretRect.origin.y + (caretRect.height / 2)
        let screenHeight = UIScreen.main.bounds.height
        guard let kbSize = self.keyboardSize else { return }
        let keyboardHeight = kbSize.height
        let visibleTextAreaHeight = screenHeight - keyboardHeight - topMenuView.frame.height
        let finalRectY = middleOfCaretHeight - topMenuView.frame.height - (visibleTextAreaHeight / 2)


        let finalRect = CGRect(x: 0.0, y: finalRectY, width: textView.frame.width, height: visibleTextAreaHeight)

        textView.scrollRectToVisible(finalRect, animated: false)
    }
}
szooky
  • 177
  • 1
  • 12
  • 1
    I'm having a hard time understanding the intended effect. By your code, it looks like you're taking a bunch of screen measurements. That's good, but what progress have you made? What is happening right now with the code you have? What are you expecting to happen, and what is actually happening? – Cody Potter Dec 15 '17 at 01:32
  • I would implement it by wrapping the `UITextView` into a `UIScrollView`. – kelin Dec 16 '17 at 11:35
  • @kelin no need to wrap it in UIScrollView, if you check the UITextField class definition you'll see it actually already a subclass of UIScrollView. – Andras M. Dec 16 '17 at 19:26

1 Answers1

4

Here is what I would do:

First set up these in viewDid load

override func viewDidLoad() {
    super.viewDidLoad()
    textView.delegate = self
    textView.isEditable = true
    textView.isScrollEnabled = false
}

Then add this extension:

extension ViewController: UITextViewDelegate {
func trackCaret(_ textView:UITextView){
    if let selectedRange = textView.selectedTextRange {
        let caretRect = textView.caretRect(for: selectedRange.start)
        let yPos = caretRect.origin.y - (textView.frame.height/2)
        let xPos = caretRect.origin.x - (textView.frame.width/2)
        textView.bounds.origin = CGPoint(x: xPos, y: yPos)
    }
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    trackCaret(textView)
    return true
}
func textViewDidBeginEditing(_ textView: UITextView) {
    trackCaret(textView)
}
func textViewDidEndEditing(_ textView: UITextView) {
    // If you don't need to move back to the original position you can leave this out
    //  textView.bounds.origin = CGPoint.zero
    //or if you want smooth animated scroll back then
    textView.scrollRectToVisible(CGRect(x:0,
                                        y:0,
                                        width: textView.bounds.width,
                                        height: textView.bounds.height),
                                 animated: true)
}

}

With this you get the typewriter effect without jumping all over the place. The didendEditing method is only there to scroll back to origin 0,0. If you don't need to do it just remove it.

Andras M.
  • 838
  • 7
  • 12
  • That's almost what I was looking for, however I had to do some modifications, I wanted to lock only `yPosition` of caret (text shouldn't move from right to left) - easy to fix (just changed `xPos` to 0) and I also want to have ability to scroll text manually so I left `textView.isScrollEnabled` on. I also moved `trackCaret` to `textViewDidChangeSelection` from `shouldChangeTextIn`. It now works kinda well, but still there are some glitches, for example when I double tap space and textview changes it to dot scroll goes way off screen for some reason... – szooky Dec 18 '17 at 12:18
  • I guess you could turn off autocorrect for the textview. Weird things could also happen if the user pastes in text instead of typing. These small things you need to decide how you want to handle. – Andras M. Dec 18 '17 at 19:54