如何实现UITextView随文本自适应大小且支持长文本滚动?
Got it, let's tackle this UITextView auto-sizing problem—this is such a common scenario for input fields, and I've got two reliable approaches to share with you. The core goal here is to let the text view grow with its content until it hits a maximum height, then lock it and enable scrolling.
Approach 1: Auto Layout + Dynamic Height Calculation (No Subclassing Needed)
This is my go-to method because it's straightforward and works with both Storyboard/XIB and code-created text views.
Step 1: Set Up Constraints
First, configure your UITextView's constraints:
- Pin the
top,leading, andtrailingedges to your parent view (or other elements) with your desired padding (e.g., 16pt). - Add a minimum height constraint (e.g., 44pt—follows iOS's touch target guidelines).
- Add a maximum height constraint (e.g., 200pt, adjust based on your UI needs) and set its priority to
750(lower than the default1000required priority). This lets Auto Layout override it when the content is shorter than the max height.
Step 2: Listen for Text Changes
You can use either the UITextViewDelegate or NotificationCenter to detect when the text updates. Here's how to implement both:
Using Delegate:
import UIKit class ViewController: UIViewController, UITextViewDelegate { @IBOutlet weak var inputTextView: UITextView! @IBOutlet weak var textViewMaxHeightConstraint: NSLayoutConstraint! override func viewDidLoad() { super.viewDidLoad() inputTextView.delegate = self inputTextView.font = .systemFont(ofSize: 16) // Set your desired font // Initial height check adjustTextViewSizeAndScroll() } func textViewDidChange(_ textView: UITextView) { adjustTextViewSizeAndScroll() } private func adjustTextViewSizeAndScroll() { // Calculate the height needed to fit all content let requiredHeight = inputTextView.sizeThatFits( CGSize(width: inputTextView.bounds.width, height: CGFloat.greatestFiniteMagnitude) ).height // Toggle scrolling and adjust height if requiredHeight <= textViewMaxHeightConstraint.constant { inputTextView.isScrollEnabled = false // Let Auto Layout use the required height (max height constraint has lower priority, so it'll be ignored here) inputTextView.heightAnchor.constraint(equalToConstant: requiredHeight).isActive = true } else { inputTextView.isScrollEnabled = true // Re-enforce the max height constraint textViewMaxHeightConstraint.isActive = true } // Force immediate layout update inputTextView.layoutIfNeeded() } }
Using NotificationCenter:
If you prefer not to use the delegate, replace the delegate setup with this in viewDidLoad:
NotificationCenter.default.addObserver( self, selector: #selector(adjustTextViewSizeAndScroll), name: UITextView.textDidChangeNotification, object: inputTextView )
And mark adjustTextViewSizeAndScroll with @objc.
Approach 2: Subclass UITextView (Override Intrinsic Content Size)
If you want a reusable component, subclassing UITextView to handle intrinsic size updates is a clean option.
Step 1: Create the Subclass
class AutoSizingTextView: UITextView { override var intrinsicContentSize: CGSize { // Calculate the size needed to fit all content let contentSize = self.sizeThatFits( CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude) ) // Return width as "no intrinsic metric" so it respects leading/trailing constraints return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height) } override func layoutSubviews() { super.layoutSubviews() // Invalidate intrinsic size if bounds change (e.g., device rotation) if bounds.size != intrinsicContentSize { invalidateIntrinsicContentSize() } } }
Step 2: Use the Subclass
Set up constraints the same way as Approach 1 (min height, max height with priority 750), then add code to toggle scrolling:
class ViewController: UIViewController, UITextViewDelegate { @IBOutlet weak var autoSizingTextView: AutoSizingTextView! @IBOutlet weak var maxHeightConstraint: NSLayoutConstraint! override func viewDidLoad() { super.viewDidLoad() autoSizingTextView.delegate = self updateScrollStatus() } func textViewDidChange(_ textView: UITextView) { updateScrollStatus() } private func updateScrollStatus() { let contentHeight = autoSizingTextView.intrinsicContentSize.height autoSizingTextView.isScrollEnabled = contentHeight > maxHeightConstraint.constant } }
Key Notes to Avoid Issues
- Font Consistency: Make sure the font you set on the UITextView matches what you use for calculations—
sizeThatFitsrelies on the current font to compute height. - Edge Insets: If you've set
textContainerInsetorcontentInset, adjust your height calculation to account for those (add the top/bottom insets to the required height). - Initial State: Always run the height/scroll check once in
viewDidLoadto set the correct initial state for pre-filled text.
Hope this solves your problem! If you run into edge cases (like handling attributed text or dynamic type), feel free to ask for tweaks.
内容的提问来源于stack exchange,提问作者Jay Patel

