How to lose margin/padding in UITextView?

Up-to-date for 2021

It is one of the silliest bugs in iOS.

The class given here, UITextViewFixed , is used widely, and is usually the most reasonable solution overall.

Here is the class:

@IBDesignable class UITextViewFixed: UITextView {
    override func layoutSubviews() {
        super.layoutSubviews()
        setup()
    }
    func setup() {
        textContainerInset = UIEdgeInsets.zero
        textContainer.lineFragmentPadding = 0
    }
}

Don't forget to turn off scrollEnabled in the Inspector!

  1. The solution works properly in storyboard

  2. The solution works properly at runtime

That's it, you're done.

In general, that should be all you need in most cases.

Even if you are changing the height of the text view on the fly, UITextViewFixed usually does all you need.

(A common example of changing the height on the fly, is changing it as the user types.)

Here is the broken UITextView from Apple...

Screenshot of Interface Builder with UITextView

Here is UITextViewFixed:

Screenshot of Interface Builder with UITextViewFixed

Note that of course you must...

...turn off scrollEnabled in the Inspector!

(Turning on scrollEnabled means "make this view expand as much as possible vertically by expanding the bottom margin as much as possible.")


Some further issues

(1) In some very unusual cases dynamically changing heights, Apple does a bizarre thing: they add extra space at the bottom.

No, really! This would have to be one of the most infuriating things in iOS.

If you encounter the problem, here is a "quick fix" which usually helps:

...
        textContainerInset = UIEdgeInsets.zero
        textContainer.lineFragmentPadding = 0

        // this is not ideal, but sometimes this "quick fix"
        // will solve the "extra space at the bottom" insanity:
        var b = bounds
        let h = sizeThatFits(CGSize(
           width: bounds.size.width,
           height: CGFloat.greatestFiniteMagnitude)
       ).height
       b.size.height = h
       bounds = b
 ...

(2) In rare cases, to fix yet another subtle mess-up by Apple, you have to add:

override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) {
    super.setContentOffset(contentOffset, animated: false) // sic
}

(3) Arguably, we should be adding:

contentInset = UIEdgeInsets.zero

just after .lineFragmentPadding = 0 in UITextViewFixed.

However ... believe or not ... that just doesn't work in current iOS! (Checked 2021.) It may be necessary to add that line in the future.

The fact that UITextView is broken in iOS is one of the weirdest things in all of mobile computing. Ten year anniversary of this question and it's still not fixed!

Finally, here's a somewhat similar tip for TextField: Set the maximum character length of a UITextField in Swift

Completely random tip: how to add the "..." on the end

Often you are using a UITextView "like a UILabel". So you want it to truncate text using an ellipsis, "..."

If so, add:

 textContainer.lineBreakMode = .byTruncatingTail

Handy tip if you want zero height, when, there's no text at all

Often you use a text view to only display text. So, you use lines "0" to mean the text view will automatically change height depending on how many lines of text.

That's great. But if there is no text at all, then unfortunately you get the same height as if there is one line of text!!!! The text view never "goes away".

Enter image description here

If you want it to "go away", just add this

override var intrinsicContentSize: CGSize {
    var i = super.intrinsicContentSize
    print("for \(text) size will be \(i)")
    if text == "" { i.height = 1.0 }
    print("   but we changed it to \(i)")
    return i
}

Enter image description here

(I made it '1' height, so it's clear what's going on in that demo, '0' is fine.)

What about UILabel?

When just displaying text, UILabel has many advantages over UITextView. UILabel does not suffer from the problems described on this Q&A page.

Indeed the reason we all usually "give up" and just use UITextView is that UILabel is difficult to work with. In particular it is ridiculously difficult to just add padding, correctly, to UILabel.

In fact here is a full discussion on how to "finally" correctly add padding to UILabel: Adding space/padding to a UILabel. In some cases if you are doing a difficult layout with dynamic height cells, it is sometimes better to do it the hard way with UILabel.


Building off some of the good answers already given, here is a purely Storyboard / Interface Builder-based solution that works in iOS 7.0+

Set the UITextView's User Defined Runtime Attributes for the following keys:

textContainer.lineFragmentPadding
textContainerInset

Interface Builder


For iOS 7.0, I've found that the contentInset trick no longer works. This is the code I used to get rid of the margin/padding in iOS 7.

This brings the left edge of the text to the left edge of the container:

textView.textContainer.lineFragmentPadding = 0

This causes the top of the text to align with the top of the container:

textView.textContainerInset = .zero

Both lines are needed to completely remove the margin/padding.


This workaround was written in 2009 when iOS 3.0 was released. It no longer applies.

I ran into the exact same problem, and in the end I had to wind up using

nameField.contentInset = UIEdgeInsetsMake(-4, -8, 0, 0);

where nameField is a UITextView. The font I happened to be using was Helvetica 16 point. It’s only a custom solution for the particular field size I was drawing. This makes the left offset flush with the left side, and the top offset where I want it for the box it’s drawn in.

In addition, this only seems to apply to UITextViews where you are using the default aligment, i.e.,

nameField.textAlignment = NSTextAlignmentLeft;

Align to the right for example and the UIEdgeInsetsMake seems to have no impact on the right edge at all.

At very least, using the .contentInset property allows you to place your fields with the "correct" positions, and accommodate the deviations without offsetting your UITextViews.