如何使 UITextView 高度动态根据文本长度?

正如你在这张图片中看到的

UITextView根据文本长度改变它的高度,我想让它根据文本长度调整它的高度。

我看到了其他问题,但解决方案对我不起作用 *

example

146602 次浏览

Give this a try:

CGRect frame = self.textView.frame;
frame.size.height = self.textView.contentSize.height;
self.textView.frame = frame;

Edit- Here's the Swift:

var frame = self.textView.frame
frame.size.height = self.textView.contentSize.height
self.textView.frame = frame

it's straight forward to do in programatic way. just follow these steps

  1. add an observer to content length of textfield

    [yourTextViewObject addObserver:self forKeyPath:@"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL];
    
  2. implement observer

    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    UITextView *tv = object;
    
    
    //Center vertical alignment
    CGFloat topCorrect = ([tv bounds].size.height - [tv contentSize].height * [tv zoomScale])/2.0;
    topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
    tv.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
    
    
    
    
    mTextViewHeightConstraint.constant = tv.contentSize.height;
    
    
    [UIView animateWithDuration:0.2 animations:^{
    
    
    [self.view layoutIfNeeded];
    }];
    
    
    }
    
  3. if you want to stop textviewHeight to increase after some time during typing then implement this and set textview delegate to self.

    -(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
    {
    if(range.length + range.location > textView.text.length)
    {
    return NO;
    }
    
    
    NSUInteger newLength = [textView.text length] + [text length] - range.length;
    
    
    return (newLength > 100) ? NO : YES;
    
    
    }
    

this Works for me, all other solutions didn't.

func adjustUITextViewHeight(arg : UITextView) {
arg.translatesAutoresizingMaskIntoConstraints = true
arg.sizeToFit()
arg.scrollEnabled = false
}

In Swift 4 the syntax of arg.scrollEnabled = false has changed to arg.isScrollEnabled = false.

Followed by DeyaEldeen's answer.
In my case. I grow the textview height automatically by adding

swift 3

textView.translatesAutoresizingMaskIntoConstraints = false textView.isScrollEnabled = false

Here are two pitfalls in iOS 8.3 when coming with textView.textContainer.maximumNumberOfLines = 10

Refer to my gist, please.

textView.attributedText = originalContent


let lineLimit = 10
textView.isEditable = true
textView.isScrollEnabled = false
textView.textContainerInset = .zero // default is (8, 0, 8, 0)
textView.textContainer.maximumNumberOfLines = lineLimit // Important condition
textView.textContainer.lineBreakMode = .byTruncatingTail


// two incomplete methods, which do NOT work in iOS 8.3


// size.width可能比maxSize.width小 ————遗憾的是 iOS 8.3 上此方法无视maximumNumberOfLines参数,所以得借助于UILabel
// size.width may be less than maxSize.width, ---- Do NOT work in iOS 8.3, which disregards textView.textContainer.maximumNumberOfLines
// let size = textView.sizeThatFits(maxSize)


// 遗憾的是 iOS 8.3 上此方法失效了,得借助于UILabel
// Does not work in iOS 8.3
// let size = textView.layoutManager.usedRectForTextContainer(textView.textContainer).size


// Suggested method: use a temperary label to get its size
let label = UILabel(); label.attributedText = originalContent
let size = label.textRect(forBounds: CGRect(origin: .zero, size: maxSize), limitedToNumberOfLines: lineLimit).size
textView.frame.size = size

Swift 4

Add It To Your Class

UITextViewDelegate

func textViewDidChange(_ textView: UITextView) {
let fixedWidth = textView.frame.size.width
textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
var newFrame = textView.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
textView.frame = newFrame
}

its working

func textViewDidChange(_ textView: UITextView) {
let fixedWidth = textviewconclusion.frame.size.width
textviewconclusion.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
let newSize = textviewconclusion.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
var newFrame = textviewconclusion.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
textviewconclusion.frame = newFrame
}

1 Add an observer to the content length of textfield

   yourTextView.addObserver(self, forKeyPath: "contentSize", options: (NSKeyValueObservingOptions.new), context: nil);

2 Implement observer

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
let tv = object as! UITextView;
var topCorrect = (tv.bounds.size.height - tv.contentSize.height * tv.zoomScale)/2.0;
topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );
tv.contentOffset.x = 0;
tv.contentOffset.y = -topCorrect;
self.yourTextView.contentSize.height = tv.contentSize.height;
UIView.animate(withDuration: 0.2, animations: {
self.view.layoutIfNeeded();
});
}

SWIFT 4

Change the size when typing

UITextViewDelegate

func textViewDidChange(_ textView: UITextView) {
yourTextView.translatesAutoresizingMaskIntoConstraints = true
yourTextView.sizeToFit()
yourTextView.isScrollEnabled = false


let calHeight = yourTextView.frame.size.height
yourTextView.frame = CGRect(x: 16, y: 193, width: self.view.frame.size.width - 32, height: calHeight)
}

Change the size when load

func textViewNotasChange(arg : UITextView) {
arg.translatesAutoresizingMaskIntoConstraints = true
arg.sizeToFit()
arg.isScrollEnabled = false


let calHeight = arg.frame.size.height
arg.frame = CGRect(x: 16, y: 40, width: self.view.frame.size.width - 32, height: calHeight)
}

Call the function of the second option like this:

textViewNotasChange(arg: yourTextView)

just make a connection with your textView's height Constraint

@IBOutlet var textView: UITextView!
@IBOutlet var textViewHeightConstraint: NSLayoutConstraint!

and use this code below

textViewHeightConstraint.constant = self.textView.contentSize.height

In Storyboard / Interface Builder simply disable scrolling in the Attribute inspector.

In code textField.scrollEnabled = false should do the trick.

In my project, the view controller is involved with lots of Constraints and StackView, and I set the TextView height as a constraint, and it varies based on the textView.contentSize.height value.

step1: get a IB outlet

@IBOutlet weak var textViewHeight: NSLayoutConstraint!

step2: use the delegation method below.

extension NewPostViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
textViewHeight.constant = self.textView.contentSize.height + 10
}
}

Better yet swift 4 add as an extension:

extension UITextView {
func resizeForHeight(){
self.translatesAutoresizingMaskIntoConstraints = true
self.sizeToFit()
self.isScrollEnabled = false
}
}

If your textView is allowed to grow as tall as the content, then

textView.isScrollEnabled = false

should just work with autolayout.

If you want to remain the textView to be scrollable, you need to add an optional height constraint,

internal lazy var textViewHeightConstraint: NSLayoutConstraint = {
let constraint = self.textView.heightAnchor.constraint(equalToConstant: 0)
constraint.priority = .defaultHigh
return constraint
}()


public override func layoutSubviews() {
super.layoutSubviews()


// Assuming there is width constraint setup on the textView.
let targetSize = CGSize(width: textView.frame.width, height: CGFloat(MAXFLOAT))
textViewHeightConstraint.constant = textView.sizeThatFits(targetSize).height
}

The reason to override layoutSubviews() is to make sure the textView is laid out properly horizontally so we can rely on the width to calculate the height.

Since the height constraint is set to a lower priority, if it runs out space vertically the actual height of the textView will be less than the contentSize. And the textView will be scrollable.

Swift 4+

This is extremely easy with autolayout! I'll explain the most simple use case. Let's say there is only a UITextView in your UITableViewCell.

  • Fit the textView to the contentView with constraints.
  • Disable scrolling for the textView.
  • Update the tableView on textViewDidChange.

That's all!

protocol TextViewUpdateProtocol {
func textViewChanged()
}


class TextViewCell: UITableViewCell {
    

//MARK: Reuse ID
static let identifier = debugDescription()
    

//MARK: UI Element(s)
/// Reference of the parent table view so that it can be updated
var textViewUpdateDelegate: TextViewUpdateProtocol!
    

lazy var textView: UITextView = {
let textView = UITextView()
textView.isScrollEnabled = false
textView.delegate = self
textView.layer.borderColor = UIColor.lightGray.cgColor
textView.layer.borderWidth = 1
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
}()
    

//MARK: Padding Variable(s)
let padding: CGFloat = 50
    

//MARK: Initializer(s)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
        

addSubviews()
addConstraints()
        

textView.becomeFirstResponder()
}
    

//MARK: Helper Method(s)
func addSubviews() {
contentView.addSubview(textView)
}
    

func addConstraints() {
textView.leadingAnchor  .constraint(equalTo: contentView.leadingAnchor, constant: padding).isActive = true
textView.trailingAnchor .constraint(equalTo: contentView.trailingAnchor, constant: -padding).isActive = true
textView.topAnchor      .constraint(equalTo: contentView.topAnchor, constant: padding).isActive = true
textView.bottomAnchor   .constraint(equalTo: contentView.bottomAnchor, constant: -padding).isActive = true
}
    

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
    

}


extension TextViewCell: UITextViewDelegate {
    

func textViewDidChange(_ textView: UITextView) {
textViewUpdateDelegate.textViewChanged()
}
    

}

Now you have to inherit implement the protocol in your ViewController.

extension ViewController: TextViewUpdateProtocol {
    

func textViewChanged() {
tableView.beginUpdates()
tableView.endUpdates()
}
    

}

Check out my repo for the full implementation.

This answer may be late but I hope it helps someone.

For me, these 2 lines of code worked:

textView.isScrollEnabled = false
textView.sizeToFit()

But don't set height constraint for your Textview

Swift 5, Use extension:

extension UITextView {
func adjustUITextViewHeight() {
self.translatesAutoresizingMaskIntoConstraints = true
self.sizeToFit()
self.isScrollEnabled = false
}
}

Usecase:

textView.adjustUITextViewHeight()

And don't care about the height of texeView in the storyboard (just use a constant at first)

All I had to do was:

  1. Set the constraints to the top, left, and right of the textView.
  2. Disable scrolling in Storyboard.

This allows autolayout to dynamically size the textView based on its content.

Declaration here

    fileprivate weak var textView: UITextView!

Call your setupview here

    override func viewDidLoad() {
super.viewDidLoad()


setupViews()
}

Setup here

    fileprivate func setupViews() {


let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.text = "your text here"
textView.font = UIFont.poppinsMedium(size: 14)
textView.textColor = UIColor.brownishGrey
textView.textAlignment = .left
textView.isEditable = false
textView.isScrollEnabled = false
textView.textContainerInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)


self.view.addSubview(textView)


self.textView = textView


setupConstraints()


}

Setup constraints here

   fileprivate func setupConstraints() {


NSLayoutConstraint.activate([


textView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
textView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20),
textView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20),
textView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20),
textView.heightAnchor.constraint(greaterThanOrEqualToConstant: 150),
])
}

Whenever you need to resize the textview according to the inside content size, like in messageing app. Use cocoapods(GrowingTextView), it will make your life easier, than coding the dynamic resizing of textview on your own.

I added these two lines of code and work fine for me.

Works in Swift 5+

func adjustUITextViewHeight(textView : UITextView)
{
textView.translatesAutoresizingMaskIntoConstraints = true
textView.isScrollEnabled = false
textView.sizeToFit()
}
  1. Put textView in StackView
  2. Set constraints(top, bottom, left, right) for stackView
  3. Add height constraint to StackView, select this constraint and set it 'Greater Than or Equal' in Relation, on the right panel

if you want preload textview on controller load. Call this function from view didload() func textViewDidChange(_ textView: UITextView) {

    let esmitated = CGSize(width: txtView.frame.width, height: .infinity)
let esmitatedSize = txtView.sizeThatFits(esmitated)
self.heghtConstraint.constant = esmitatedSize.height

}