Search code examples
swiftscrollviewuitextviewcursor-position

Moving ScrollView/Keyboard to UITextView cursor position


I am working on scrolling the keyboard to the current position of the cursor in a UITextView.

There for I am using the textViewDidChange like this:

func textViewDidChange(_ textView: UITextView) {
        if let cursorPosition = textView.selectedTextRange?.end {

            let caretPositionRect = textView.caretRect(for: cursorPosition)
            print(caretPositionRect, "caret")
            DispatchQueue.main.async{ [weak self] in
                let pointsuperview = textView.convert(caretPositionRect, to: self?.vc?.mainView.scrollView)
                self?.vc?.mainView.scrollView.scrollRectToVisible(pointsuperview, animated: false)
                print(pointsuperview, "ps")
            }
        }
    }

It works as long as there is a character or if I am going back. But if I am adding a new line by pressing enter to the last line I get an output like this:

(inf, inf, 0.0, 0.0) caret

When I am then using the backspace I get valid values again.

valid values look like this:

(4.0, 7.0, 2.0, 21.5) caret

Same result when using selectedTextRange.start

I tried solutions from this question: Getting and Setting Cursor Position of UITextField and UITextView in Swift


Solution

  • I had the same problem until I put the Dispatch Queue before the textview.caretRect(for:)

    func textViewDidChange(_ textView: UITextView) {
            if let cursorPosition = textView.selectedTextRange?.end {
    
                DispatchQueue.main.async{ [weak self] in
                    let caretPositionRect = textView.caretRect(for: cursorPosition)
                    print(caretPositionRect, "caret")
                    let pointsuperview = textView.convert(caretPositionRect, to: self?.vc?.mainView.scrollView)
                    self?.vc?.mainView.scrollView.scrollRectToVisible(pointsuperview, animated: false)
                    print(pointsuperview, "ps")
                }
            }
        }
    

    if it doesn't work try adding a delay of 1 millisecond

    func textViewDidChange(_ textView: UITextView) {
            if let cursorPosition = textView.selectedTextRange?.end {
    
            let deadlineTime = DispatchTime.now() + .milliseconds(1)
            DispatchQueue.main.asyncAfter(deadline: deadlineTime) { [weak self] in
                    let caretPositionRect = textView.caretRect(for: cursorPosition)
                    print(caretPositionRect, "caret")
                    let pointsuperview = textView.convert(caretPositionRect, to: self?.vc?.mainView.scrollView)
                    self?.vc?.mainView.scrollView.scrollRectToVisible(pointsuperview, animated: false)
                    print(pointsuperview, "ps")
                }
            }
        }