Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scroll to the bottom of textView programmatically

Tags:

ios

swift

I made a button that adds some text to a textView and I want it to automatically scroll to the bottom as it is pressed so that user would be able to see the new text added.
I can't use this solution in Swift because I don't know Objective-C. Does anyone know how can I scroll to the bottom of a textView in Swift? Thanks.

like image 775
bar5um Avatar asked Oct 30 '15 00:10

bar5um


5 Answers

Tried both content offset and scrolltoview solutions, get mixed results and choppy scrolling; having looked around the below seemed to work and produce consistent scrolling to the bottom when needed.

In viewdidload:

self.storyTextView.layoutManager.allowsNonContiguousLayout = false

Then when needed:

let stringLength:Int = self.storyTextView.text.characters.count
self.storyTextView.scrollRangeToVisible(NSMakeRange(stringLength-1, 0))
like image 191
DavidS Avatar answered Nov 07 '22 23:11

DavidS


Swift 4

let bottom = NSMakeRange(textLog.text.count - 1, 1)
textLog.scrollRangeToVisible(bottom)

Swift 3

let bottom = NSMakeRange(textLog.text.characters.count - 1, 1)
textLog.scrollRangeToVisible(bottom)

Update: thanks @AntoineRucquoy for Swift 4 reminder!

like image 18
superarts.org Avatar answered Nov 08 '22 00:11

superarts.org


Simply, where myTextView is the UITextView in question:

let bottom = myTextView.contentSize.height

myTextView.setContentOffset(CGPoint(x: 0, y: bottom), animated: true) // Scrolls to end
like image 5
Sarreph Avatar answered Nov 07 '22 23:11

Sarreph


So if you click the link you posted the accepted answer shows this objective-C code:

-(void)scrollTextViewToBottom:(UITextView *)textView 
{
  if(textView.text.length > 0 ) 
  {
    NSRange bottom = NSMakeRange(textView.text.length -1, 1);
    [textView scrollRangeToVisible:bottom];
  }
}

So your challenge is to convert that code to Swift.

Break it into pieces and tackle them one at a time. First, the method definition itself.

The method is called scrollTextViewToBottom. It takes a UITextView as a parameter, and does not return a result. How would you write that method definition in Swift?

Next look that the body of the method. The if statement should be exactly the same in Swift. The creation of an NSRange is all but identical. You just need to change it a little bit:

let bottom = NSMakeRange(textView.text.length -1, 1)

The part that's probably the hardest for somebody who doesn't know Objective-C is the method call. It's sending the message scrollRangeToVisible to the object textView. The parameter passed is bottom. See if you can rewrite that line in Swift. Then put the whole thing together.

like image 4
Duncan C Avatar answered Nov 08 '22 00:11

Duncan C


I use the following in an app that scrolls to the bottom automatically when text is added:

First when initializing your textView, do the following:

textView.layoutManager.allowsNonContiguousLayout = false
textView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)

Then add the following observer method:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    var bottom = textView.contentSize.height - textView.frame.size.height
    if bottom < 0 {
        bottom = 0
    }
    if textView.contentOffset.y != bottom {
        textView.setContentOffset(CGPoint(x: 0, y: bottom), animated: true)
    }
}

setting allowsNonContiguousLayout to false fixed contentSize problems for me.

Adding the contentSize observer will observe for any new changes in the contentSize of the textView and call the -observeValue(forKeyPath...) function when changes are made.

In the -observeValue(...) function, we first get the bottom (y contentOffset when fully scrolled to the bottom). We then check if that value is negative, meaning that the contentSize height is smaller than the textView frame height and you can't really do any scrolling. If you try to programmatically scroll with that negative value, it will cause that infamous jitter that many people know and love. So to avoid this jitter we simply set the value to what it already should be, 0 or you can also just return.

Then we just test to see if the contentOffset doesn't already equal the bottom value, we give it that new value. This avoids setting the contentOffset when it doesn't need to be set.

like image 3
jacob Avatar answered Nov 07 '22 23:11

jacob