以编程方式滚动到textView的底部

时间:2022-08-24 23:25:51

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.

我创建了一个按钮,为textView添加了一些文本,我希望它在按下时自动滚动到底部,以便用户能够看到添加的新文本。我无法在Swift中使用此解决方案,因为我不了解Objective-C。有谁知道如何在Swift中滚动到textView的底部?谢谢。

10 个解决方案

#1


18  

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.

尝试内容偏移和scrolltoview解决方案,获得混合结果和不连贯的滚动;看了看下面似乎工作,并在需要时产生一致的滚动到底部。

In viewdidload:

在viewdidload中:

self.storyTextView.layoutManager.allowsNonContiguousLayout = false

Then when needed:

然后在需要时:

let stringLength:Int = self.storyTextView.text.characters.count
self.storyTextView.scrollRangeToVisible(NSMakeRange(stringLength-1, 0))

#2


7  

Swift 4

斯威夫特4

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

Swift 3

斯威夫特3

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

Update: thanks @AntoineRucquoy for Swift 4 reminder!

更新:感谢@AntoineRucquoy为Swift 4提醒!

#3


4  

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

因此,如果您单击您发布的链接,则接受的答案显示此目标-C代码:

-(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.

所以你的挑战是将代码转换为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?

该方法称为scrollTextViewToBottom。它将UITextView作为参数,并且不返回结果。你会如何在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:

接下来看一下该方法的主体。在Swift中if语句应该完全相同。 NSRange的创建几乎完全相同。你只需要稍微改变一下:

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.

对于不了解Objective-C的人来说,最难的部分是方法调用。它将消息scrollRangeToVisible发送到对象textView。传递的参数是底部。看看你是否可以在Swift中重写该行。然后把整个东西放在一起。

#4


4  

Simply, where myTextView is the UITextView in question:

简单地说,myTextView是有问题的UITextView:

let bottom = myTextView.contentSize.height

myTextView.setContentOffset(CGPoint(x: 0, y: bottom), animated: true) // Scrolls to end

#5


2  

Language:Swift

语言:斯威夫特

Follow steps as below:
//Declare

按照以下步骤操作://声明

@IBOutlet weak var trTextDataRead: UITextView!  

//Cunstom method

// Cunstom方法

func insertTextView(text: String){
 //Insert text
 trTextDataRead.text.append(text)  

 //Scroll to the end  
 let btm = NSMakeRange(trTextDataRead.text.lengthOfBytes(using: String.Encoding.utf8), 0)  
 trTextDataRead.scrollRangeToVisible(btm)
}

#6


0  

UITextView has a property contentOffsent. You can either set textView.contentOffset or textView.setContentOffset(offset, animated: true)

UITextView具有属性contentOffsent。您可以设置textView.contentOffset或textView.setContentOffset(offset,animated:true)

For example if the contentSize of your text view is (100, 500) but the height of the text view is only 100, then to scroll to the bottom, set the contentOffset property to (0, 400) (this is for a vertical text view). More generically the formula for scrolling to the bottom is textView.contentSize.height-textView.height. Every time your button is pressed, set the offset.

例如,如果文本视图的contentSize为(100,500)但文本视图的高度仅为100,则要滚动到底部,请将contentOffset属性设置为(0,400)(这是针对垂直文本的)视图)。更一般地,滚动到底部的公式是textView.contentSize.height-textView.height。每按一次按钮,设置偏移量。

I would also really recommend reading the documentation and trying to figure it out. Swift and iOS is quite well documented and a question like this is easily searchable via Google.

我也真的建议阅读文档并试图找出它。 Swift和iOS有很好的文档记录,这样的问题很容易通过Google搜索到。

Edit: This works because UITextView inherits from UIScrollView.

编辑:这是有效的,因为UITextView继承自UIScrollView。

Sidenote: I wrote a UITextView subclass where you can set the vertical text alignment so if you set the text alignment to .Bottom, the text will align with the bottom of the view.

旁注:我编写了一个UITextView子类,您可以在其中设置垂直文本对齐方式,因此如果将文本对齐方式设置为.Bottom,则文本将与视图的底部对齐。

class TextView: UITextView {

    enum VerticalAlignment: Int {
        case Top = 0, Middle, Bottom
    }

    var verticalAlignment: VerticalAlignment = .Middle

    //override contentSize property and observe using didSet
    override var contentSize: CGSize {
        didSet {
            let textView = self
            let height = textView.bounds.size.height
            let contentHeight:CGFloat = contentSize.height
            var topCorrect: CGFloat = 0.0
            switch(self.verticalAlignment){
            case .Top:
                textView.contentOffset = CGPointZero //set content offset to top
           case .Middle:
                topCorrect = (height - contentHeight * textView.zoomScale)/2.0
                topCorrect = topCorrect < 0 ? 0 : topCorrect
                textView.contentOffset = CGPoint(x: 0, y: -topCorrect)
           case .Bottom:
                topCorrect = textView.bounds.size.height - contentHeight
                topCorrect = topCorrect < 0 ? 0 : topCorrect
                textView.contentOffset = CGPoint(x: 0, y: -topCorrect)
            }
            if contentHeight >= height { //if the contentSize is greater than the height
                topCorrect = contentHeight - height //set the contentOffset to be the
                topCorrect = topCorrect < 0 ? 0 : topCorrect //contentHeight - height of textView
                textView.contentOffset = CGPoint(x: 0, y: topCorrect)
            }
        }
    }

    // MARK: - UIView

    override func layoutSubviews() {
        super.layoutSubviews()
        let size = self.contentSize //forces didSet to be called
        self.contentSize = size
    }
}

In the above example (pulled directly from my subclass), you'll notice I make extensive use of the contentOffset property. I do some calculations to figure out where the offset should be based on the vertical alignment property and then set the content offset property according (which is how you programmatically scroll with a scroll view)

在上面的例子中(直接从我的子类中提取),你会注意到我大量使用了contentOffset属性。我做了一些计算来确定偏移应该基于垂直对齐属性的位置,然后根据(通过滚动视图以编程方式滚动的方式设置内容偏移属性)

#7


0  

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时,请执行以下操作:

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.

设置allowNonContiguousLayout为false修复了contentSize问题。

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.

添加contentSize观察器将观察textView的contentSize中的任何新更改,并在进行更改时调用-observeValue(forKeyPath ...)函数。

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.

在-observeValue(...)函数中,我们首先得到底部(y完全滚动到底部时的contentOffset)。然后我们检查该值是否为负数,这意味着contentSize高度小于textView框架高度,并且您无法真正进行任何滚动。如果你尝试以编程方式滚动该负值,它将导致许多人知道和喜爱的臭名昭着的抖动。因此,为了避免这种抖动,我们只需将值设置为它应该是0,或者您也可以返回。

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.

然后我们只是测试以查看contentOffset是否已经不等于最低值,我们给它新值。这样可以避免在不需要设置时设置contentOffset。

#8


0  

If you're dealing with the UITextView's attributedText property:

如果您正在处理UITextView的attributedText属性:

in viewDidLoad()

在viewDidLoad()中

self.storyTextView.layoutManager.allowsNonContiguousLayout = false

in your scrolling method

在你的滚动方法中

let stringLength:Int = self.storyTextView.attributedText.string.characters.count
self.storyTextView.scrollRangeToVisible(NSMakeRange(stringLength-1, 0))

#9


0  

A lot of people are explaining how to scroll to the bottom, but one thing to note is that this won't work if you place it in viewDidLoad. For example: I needed to use this to scroll a log to the bottom when the page loaded. In order to do this, I had to implement the following code

很多人都在解释如何滚动到底部,但有一点需要注意的是,如果将它放在viewDidLoad中,这将无效。例如:我需要使用它在页面加载时将日志滚动到底部。为此,我必须实现以下代码

- (void) viewDidLoad {
    [super viewDidLoad];
    [_logTextView setText:[Logger loadLogText]];
}

- (void) viewDidLayoutSubviews {
    [_logTextView
     setContentOffset:CGPointMake(
                          0.0,
                          _logTextView.contentSize.height
                        - _logTextView.frame.size.height
                      )
             animated:NO
    ];
}

The actual scrolling of the UITextView cannot be done in viewDidLoad.

无法在viewDidLoad中实际滚动UITextView。

In my viewDidLoad implementation I set the text for the text box.

在我的viewDidLoad实现中,我为文本框设置了文本。

In my viewDidLayoutSubviews implementation I set the content offset for the UITextView by generating a CGPoint using the height of the text views content minus the height of the text view itself. This way, when it scrolls to the bottom, the bottom of the text is not at the top of the box and instead, the bottom of the text is at the bottom of the box.

在我的viewDidLayoutSubviews实现中,我通过使用文本视图内容的高度减去文本视图本身的高度生成CGPoint来设置UITextView的内容偏移量。这样,当它滚动到底部时,文本的底部不在框的顶部,而是文本的底部位于框的底部。

#10


0  

Swift 4

斯威夫特4

private func textViewScrollToBottom() {
    let bottomRange = NSMakeRange(self.myTextView.text.count - 1, 1)

    self.myTextView.scrollRangeToVisible(bottomRange)
}

#1


18  

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.

尝试内容偏移和scrolltoview解决方案,获得混合结果和不连贯的滚动;看了看下面似乎工作,并在需要时产生一致的滚动到底部。

In viewdidload:

在viewdidload中:

self.storyTextView.layoutManager.allowsNonContiguousLayout = false

Then when needed:

然后在需要时:

let stringLength:Int = self.storyTextView.text.characters.count
self.storyTextView.scrollRangeToVisible(NSMakeRange(stringLength-1, 0))

#2


7  

Swift 4

斯威夫特4

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

Swift 3

斯威夫特3

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

Update: thanks @AntoineRucquoy for Swift 4 reminder!

更新:感谢@AntoineRucquoy为Swift 4提醒!

#3


4  

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

因此,如果您单击您发布的链接,则接受的答案显示此目标-C代码:

-(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.

所以你的挑战是将代码转换为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?

该方法称为scrollTextViewToBottom。它将UITextView作为参数,并且不返回结果。你会如何在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:

接下来看一下该方法的主体。在Swift中if语句应该完全相同。 NSRange的创建几乎完全相同。你只需要稍微改变一下:

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.

对于不了解Objective-C的人来说,最难的部分是方法调用。它将消息scrollRangeToVisible发送到对象textView。传递的参数是底部。看看你是否可以在Swift中重写该行。然后把整个东西放在一起。

#4


4  

Simply, where myTextView is the UITextView in question:

简单地说,myTextView是有问题的UITextView:

let bottom = myTextView.contentSize.height

myTextView.setContentOffset(CGPoint(x: 0, y: bottom), animated: true) // Scrolls to end

#5


2  

Language:Swift

语言:斯威夫特

Follow steps as below:
//Declare

按照以下步骤操作://声明

@IBOutlet weak var trTextDataRead: UITextView!  

//Cunstom method

// Cunstom方法

func insertTextView(text: String){
 //Insert text
 trTextDataRead.text.append(text)  

 //Scroll to the end  
 let btm = NSMakeRange(trTextDataRead.text.lengthOfBytes(using: String.Encoding.utf8), 0)  
 trTextDataRead.scrollRangeToVisible(btm)
}

#6


0  

UITextView has a property contentOffsent. You can either set textView.contentOffset or textView.setContentOffset(offset, animated: true)

UITextView具有属性contentOffsent。您可以设置textView.contentOffset或textView.setContentOffset(offset,animated:true)

For example if the contentSize of your text view is (100, 500) but the height of the text view is only 100, then to scroll to the bottom, set the contentOffset property to (0, 400) (this is for a vertical text view). More generically the formula for scrolling to the bottom is textView.contentSize.height-textView.height. Every time your button is pressed, set the offset.

例如,如果文本视图的contentSize为(100,500)但文本视图的高度仅为100,则要滚动到底部,请将contentOffset属性设置为(0,400)(这是针对垂直文本的)视图)。更一般地,滚动到底部的公式是textView.contentSize.height-textView.height。每按一次按钮,设置偏移量。

I would also really recommend reading the documentation and trying to figure it out. Swift and iOS is quite well documented and a question like this is easily searchable via Google.

我也真的建议阅读文档并试图找出它。 Swift和iOS有很好的文档记录,这样的问题很容易通过Google搜索到。

Edit: This works because UITextView inherits from UIScrollView.

编辑:这是有效的,因为UITextView继承自UIScrollView。

Sidenote: I wrote a UITextView subclass where you can set the vertical text alignment so if you set the text alignment to .Bottom, the text will align with the bottom of the view.

旁注:我编写了一个UITextView子类,您可以在其中设置垂直文本对齐方式,因此如果将文本对齐方式设置为.Bottom,则文本将与视图的底部对齐。

class TextView: UITextView {

    enum VerticalAlignment: Int {
        case Top = 0, Middle, Bottom
    }

    var verticalAlignment: VerticalAlignment = .Middle

    //override contentSize property and observe using didSet
    override var contentSize: CGSize {
        didSet {
            let textView = self
            let height = textView.bounds.size.height
            let contentHeight:CGFloat = contentSize.height
            var topCorrect: CGFloat = 0.0
            switch(self.verticalAlignment){
            case .Top:
                textView.contentOffset = CGPointZero //set content offset to top
           case .Middle:
                topCorrect = (height - contentHeight * textView.zoomScale)/2.0
                topCorrect = topCorrect < 0 ? 0 : topCorrect
                textView.contentOffset = CGPoint(x: 0, y: -topCorrect)
           case .Bottom:
                topCorrect = textView.bounds.size.height - contentHeight
                topCorrect = topCorrect < 0 ? 0 : topCorrect
                textView.contentOffset = CGPoint(x: 0, y: -topCorrect)
            }
            if contentHeight >= height { //if the contentSize is greater than the height
                topCorrect = contentHeight - height //set the contentOffset to be the
                topCorrect = topCorrect < 0 ? 0 : topCorrect //contentHeight - height of textView
                textView.contentOffset = CGPoint(x: 0, y: topCorrect)
            }
        }
    }

    // MARK: - UIView

    override func layoutSubviews() {
        super.layoutSubviews()
        let size = self.contentSize //forces didSet to be called
        self.contentSize = size
    }
}

In the above example (pulled directly from my subclass), you'll notice I make extensive use of the contentOffset property. I do some calculations to figure out where the offset should be based on the vertical alignment property and then set the content offset property according (which is how you programmatically scroll with a scroll view)

在上面的例子中(直接从我的子类中提取),你会注意到我大量使用了contentOffset属性。我做了一些计算来确定偏移应该基于垂直对齐属性的位置,然后根据(通过滚动视图以编程方式滚动的方式设置内容偏移属性)

#7


0  

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时,请执行以下操作:

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.

设置allowNonContiguousLayout为false修复了contentSize问题。

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.

添加contentSize观察器将观察textView的contentSize中的任何新更改,并在进行更改时调用-observeValue(forKeyPath ...)函数。

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.

在-observeValue(...)函数中,我们首先得到底部(y完全滚动到底部时的contentOffset)。然后我们检查该值是否为负数,这意味着contentSize高度小于textView框架高度,并且您无法真正进行任何滚动。如果你尝试以编程方式滚动该负值,它将导致许多人知道和喜爱的臭名昭着的抖动。因此,为了避免这种抖动,我们只需将值设置为它应该是0,或者您也可以返回。

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.

然后我们只是测试以查看contentOffset是否已经不等于最低值,我们给它新值。这样可以避免在不需要设置时设置contentOffset。

#8


0  

If you're dealing with the UITextView's attributedText property:

如果您正在处理UITextView的attributedText属性:

in viewDidLoad()

在viewDidLoad()中

self.storyTextView.layoutManager.allowsNonContiguousLayout = false

in your scrolling method

在你的滚动方法中

let stringLength:Int = self.storyTextView.attributedText.string.characters.count
self.storyTextView.scrollRangeToVisible(NSMakeRange(stringLength-1, 0))

#9


0  

A lot of people are explaining how to scroll to the bottom, but one thing to note is that this won't work if you place it in viewDidLoad. For example: I needed to use this to scroll a log to the bottom when the page loaded. In order to do this, I had to implement the following code

很多人都在解释如何滚动到底部,但有一点需要注意的是,如果将它放在viewDidLoad中,这将无效。例如:我需要使用它在页面加载时将日志滚动到底部。为此,我必须实现以下代码

- (void) viewDidLoad {
    [super viewDidLoad];
    [_logTextView setText:[Logger loadLogText]];
}

- (void) viewDidLayoutSubviews {
    [_logTextView
     setContentOffset:CGPointMake(
                          0.0,
                          _logTextView.contentSize.height
                        - _logTextView.frame.size.height
                      )
             animated:NO
    ];
}

The actual scrolling of the UITextView cannot be done in viewDidLoad.

无法在viewDidLoad中实际滚动UITextView。

In my viewDidLoad implementation I set the text for the text box.

在我的viewDidLoad实现中,我为文本框设置了文本。

In my viewDidLayoutSubviews implementation I set the content offset for the UITextView by generating a CGPoint using the height of the text views content minus the height of the text view itself. This way, when it scrolls to the bottom, the bottom of the text is not at the top of the box and instead, the bottom of the text is at the bottom of the box.

在我的viewDidLayoutSubviews实现中,我通过使用文本视图内容的高度减去文本视图本身的高度生成CGPoint来设置UITextView的内容偏移量。这样,当它滚动到底部时,文本的底部不在框的顶部,而是文本的底部位于框的底部。

#10


0  

Swift 4

斯威夫特4

private func textViewScrollToBottom() {
    let bottomRange = NSMakeRange(self.myTextView.text.count - 1, 1)

    self.myTextView.scrollRangeToVisible(bottomRange)
}