0

I am trying to make an editable, multiline, UITextView that can horizontally scroll. From multiple answers I found (1, 2, 3), it seems like I should put the UITextView into a UIScrollView, so I did exactly that. However, the editing experience turned out to be awful.

Here is an MCVE:

class MyViewController: UIViewController {
    
    // connect this outlet to a scroll view that covers the whole screen
    @IBOutlet var scrollView: UIScrollView!
    var textView: UITextView!
    
    override func viewDidLoad() {
        setupTextView()
        scrollView.addSubview(textView)
    }
    
    let font = UIFont.monospacedSystemFont(ofSize: 23, weight: .regular)
    func setupTextView() {

        // My text view is going to contain a maximum of 80x25 characters, so here
        // I am calculating how much space that is going to take up
        let line = String(repeating: "a", count: 81)
        let fullText = Array(repeating: line, count: 26).joined(separator: "\n")
        let unroundedSize = (fullText as NSString).size(withAttributes: [
            .font: font
        ])
        let size = CGSize(width: ceil(unroundedSize.width), height: ceil(unroundedSize.height))
        textView = UITextView(frame: CGRect(origin: .zero, size: size))
        
        scrollView.contentSize = size
        textView.spellCheckingType = .no
        textView.dataDetectorTypes = []
        textView.font = font

        // dummy text
        textView.text = """
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras et finibus turpis.
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras et finibus turpis.
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras et finibus turpis.
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras et finibus turpis.
        """

        // the text view do not need to scroll. All the scrolling is done
        // by the scroll view
        textView.isScrollEnabled = false
    }
}

To see how awful the editing experience is, put the cursor at the end of the text, then try deleting some of the text at the end of the string by holding down the delete key. The scroll view "flickers" between where the cursor is, and the far left of the text view, i.e. these two states:

enter image description here

enter image description here

I tried to scroll to the cursor whenever the cursor moved by doing:

func textViewDidChangeSelection(_ textView: UITextView) {
    guard let range = textView.selectedTextRange else { return }
    let rect = textView.caretRect(for: range.end)
    guard !CGRect(origin: scrollView.contentOffset, size: scrollView.visibleSize).intersects(rect) else { return }
    scrollView.scrollRectToVisible(rect, animated: true)
}

but that didn't improve the awful behaviour of the delete key at all.

How can I prevent the scroll view from scrolling to the start?

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Why do you need to put your textview inside a scrollview? I have created a rich text editor and I didn't need to use a scrollview. To take care of the keyboard just add a constrain to your text view to the bottom safe area and monitor the keyboard height. You need to add/remove its height to the additional safe area insets of your view controller – Leo Dabus Jan 16 '22 at 16:57
  • This way you don't need to manually scroll the textview content. no need to monitor textViewDidChangeSelection either. – Leo Dabus Jan 16 '22 at 17:01
  • Not sure why would you need to scroll horizontally – Leo Dabus Jan 16 '22 at 17:03
  • Why don't you simply set the textview contentSize width? – Leo Dabus Jan 16 '22 at 17:09
  • `Not sure why would you need to scroll horizontally` Is that not a reasonable thing to want? I am making a code editor thing that doesn't wrap text. I would imagine that this is also useful for ASCII art editors and things like that. `To take care of the keyboard...` I know how to do that. This question is not about taking care of the keyboard. `Why don't you simply set the textview contentSize width?` I have tried that too, but that still wraps the text. @LeoDabus – Sweeper Jan 16 '22 at 17:25
  • Actually I am not sure why but I am not able to change the contentSize while isScrollEnabled is true. – Leo Dabus Jan 16 '22 at 17:54
  • I tried `textContainer.size` as well but it doesn't change. – Leo Dabus Jan 16 '22 at 17:58
  • @Sweeper - I played around a bit ... this *might* help get you on your way: https://pastebin.com/TgzN3Gzq --- It's a starting attempt, and only looks at horizontal scrolling, not vertical (but the principal should be the same). – DonMag Jan 18 '22 at 22:40
  • @DonMag Thanks! I didn't have a chance to try it out until today. It works perfectly! I have no idea what the code in `scrollRectToVisible` is doing though (it somehow even ignores the `rect` parameter!?). Despite that, I still managed to follow the same "pattern" and got vertical scrolling working too :D. – Sweeper Jan 23 '22 at 09:27

0 Answers0