How to lose margin/padding in UITextView
Asked Answered
S

24

570

I have a UITextView in my iOS application, which displays a large amount of text.

I am then paging this text by using the offset margin parameter of the UITextView.

My problem is that the padding of the UITextView is confusing my calculations as it seems to be different depending on the font size and typeface that I use.

Is it possible to remove the padding surrounding the content of the UITextView?

Sirup answered 14/4, 2009 at 7:42 Comment(3)
Note that this QA is almost ten years old! With 100,000+ views, since it's one of the stupidest problems in iOS. Just FTR I put in the current, 2017, reasonably simple / usual / accepted solution below as an answer.Volteface
I still get updates on this, having written a hardcoded workaround in 2009 when IOS 3.0 had just been released. I just edited the answer to clearly state that it is years out of date and to ignore the accepted status.Burchette
Amazingly, well over ten years now (200,000 views of this QA!) and Apple still have not fixed the combination of plain bugs, soft bugs, and bizarre behavior in UITextView. It is really one of the weirdest things in all of mobile computing development!Volteface
V
490

Up-to-date for 2023

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

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 very unusual cases when 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 2023.) 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.

Volteface answered 19/2, 2017 at 22:45 Comment(14)
Why do you say it's necessary to turn off scrollEnabled in the inspector? When I do that I seem to get even less space in the my text view.Supraorbital
The height of the frame of the scroll view is unrelated to the content size. The question posed by OP makes no mention of height. Simply trying to understand your response.Supraorbital
I've posted an updated answer to @Fattie's answer which helped me to really get rid of all insets using the special trick of enabling/disabling translatesAutoresizingMaskIntoConstraints. With this answer alone I was not able to remove all the margins (some strange bottom margin resisted to go away) in a view hierarchy using auto layout. It also deals with calculation of view sizes using systemLayoutSizeFitting, which previously returned an invalid size due to the buggy UITextViewVoccola
I'm on Xcode 10.1 and using Swift 4 and i found Fattie's solution works for me if i kept scrollEnabled turned on and i used the translatesAutoresizingMaskIntoConstraints trick mentioned in @Voccola answer/comment. With scrollEnabled turned off , it still runs and looks okay in the simulator, but it doesn't look right in the storyboardCrenelation
I had textviews in table, the ones that had single line of text didn't calculate its height correctly (autolayout). To fix it I had to override didMoveToSuperview and call setup there also.Flyspeck
@Volteface , Constraints are fine, there is always 1 or 2 extra row at the bottom. It works OK if I use regular UITextView. I just tested it with simple test project and same problem occurs. I've put UITextViewFixed in table cell with top/bottom/leading/trailing constraints. I've put test project on dropbox if you want to test it: dropbox.com/s/l7ci6j0m1apc3l5/TestApp.zip?dl=0Flyspeck
I used this in my app and it causes a recursive call between layoutSubviews and setup causing a stack overflow error! Something is wrong hereAnglophobe
I had the extra spacing at the bottom issue when using a UITableViewCell. I found that doing the sizeThatFits part in layoutSubview froze my app (infinite loop?) if I attempted to edit the text view. Instead, I did what El Horrible suggested and that works fine.Classless
Guys take care using didMoveToSuperview - although it can seemingly work in simple cases, if you don't something in layout, it will, of course, never size properly as the layout changes! Regarding the other comment, in iOS programming it's easy to accidentally have an infinite loop in your custom classes, I certainly often do it!, it is unrelated to anything here.)Volteface
Great collation of info. Unfortunately neither of the two fixes for that extra space at the bottom works for me. When UITextView contains 1 line, there's no extra bottom padding. When it contains 2 or 3 lines, there is. Using UITextViewFixed and applying all fixes you've suggested, testing both with & without the two optional ones.Temperamental
When I try your first fix for extra bottom space, the height remains the same, but text is incorrectly offset in the Y axis, as though top inset padding is unzeroed. The only working solution I've found for a tight-fitting UITextView that has dynamic self-sizing is to override intrinsicContentSize and fudge-adjust the calculated height by subtracting 0.4 * font.lineHeight, in the case where there's more than one visible line.Temperamental
As mentioned in other comments, #1 in "Some further issues" may lead to recursive call. Instead of trying to fix that extra space at the bottom in the layoutSubviews(), we should note that this behaviour is not at all unexpected. It happens because the layout engine first calculates the height before layoutSubviews() is fired. Therefore it takes into account the original top and bottom insets and sticks with the hight afterwards. With that in mind the solution should be to make the same adjustOlivas
#1 in "Some further issues" solution may lead to recursive call. That extra space at the bottom is not at all unexpected though. It happens because the layout engine first calculates the height before layoutSubviews() is fired. Therefore it takes into account the original top and bottom insets and sticks with the height afterwards. Based on that, my solution was to make the same text container adjustments in required init?(coder aDecoder: NSCoder) as proposed in layoutSubviews(). WIth that you don't have to manipulate the bounds in layoutSubviews().Olivas
@Olivas for sure, anytime at all you use layoutSubviews you risk making a mistake and ending up in a loop and crash! It's a good idea to use required init?(coder but that would also take some thought about the layout cycle and, perhaps, you'd have to be careful if code v. storyboard bringup?Volteface
B
846

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.

Barrelhouse answered 24/9, 2013 at 17:5 Comment(14)
This is working for me for two lines, but by the time I get to 5 lines, the text is getting cut off.Summand
This worked great for me, with multiple lines (10+), and using an embedded font. Thanks user1687195.Nuptials
Note that the second line can also be written as: self.descriptionTextView.textContainerInset = UIEdgeInsetsZero;Diversion
Didn't manage to remove the top/bottom padding with this solutionTaxidermy
For vertical align the content of text view with text label, the first line only works for me in iOS 7.Geelong
Setting lineFragmentPadding to 0 is the magic I was looking for. I have no idea why Apple makes it so hard to line up UITextView content with other controls.Outlaw
This is the correct answer. Scrolling horizontally doesn't happen with this solution.Burchette
Works nice in iOS7 and above. :-]Peavey
I like this solution the best. Setting the insets to hard coded values (for example: UIEdgeInsetsMake(2, 2, 2, 2)) is sketchy at best since the defaults could change with every version of the OS.Seafaring
lineFragmentPadding is not intended to modify margins. From the docs Line fragment padding is not designed to express text margins. Instead, you should use insets on your text view, adjust the paragraph margin attributes, or change the position of the text view within its superview.Maclay
This should be marked as the correct answer. I'm baffled by Apple's statement that this was not designed to express text margins. I suppose you could just set the left inset to -lineFragmentPadding instead of changing this, which makes it a bit cleaner. In any case, this solution is FAR cleaner than a bunch of hardcoded insets.Sandasandakan
@SergeyGrischyov - regarding the insane "bottom space problem", I give the only solution in my answer below. Apple madness.Volteface
While the particular point given in this answer is totally correct, it, very simply, does not "remove the stupid padding from a text view" as asked in the question. Since this QA is very old, I typed in the correct current answer below - look for the yellow screenshots! :) Hope it helps someone.Volteface
You probably also want to disable scrollingAlienism
V
490

Up-to-date for 2023

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

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 very unusual cases when 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 2023.) 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.

Volteface answered 19/2, 2017 at 22:45 Comment(14)
Why do you say it's necessary to turn off scrollEnabled in the inspector? When I do that I seem to get even less space in the my text view.Supraorbital
The height of the frame of the scroll view is unrelated to the content size. The question posed by OP makes no mention of height. Simply trying to understand your response.Supraorbital
I've posted an updated answer to @Fattie's answer which helped me to really get rid of all insets using the special trick of enabling/disabling translatesAutoresizingMaskIntoConstraints. With this answer alone I was not able to remove all the margins (some strange bottom margin resisted to go away) in a view hierarchy using auto layout. It also deals with calculation of view sizes using systemLayoutSizeFitting, which previously returned an invalid size due to the buggy UITextViewVoccola
I'm on Xcode 10.1 and using Swift 4 and i found Fattie's solution works for me if i kept scrollEnabled turned on and i used the translatesAutoresizingMaskIntoConstraints trick mentioned in @Voccola answer/comment. With scrollEnabled turned off , it still runs and looks okay in the simulator, but it doesn't look right in the storyboardCrenelation
I had textviews in table, the ones that had single line of text didn't calculate its height correctly (autolayout). To fix it I had to override didMoveToSuperview and call setup there also.Flyspeck
@Volteface , Constraints are fine, there is always 1 or 2 extra row at the bottom. It works OK if I use regular UITextView. I just tested it with simple test project and same problem occurs. I've put UITextViewFixed in table cell with top/bottom/leading/trailing constraints. I've put test project on dropbox if you want to test it: dropbox.com/s/l7ci6j0m1apc3l5/TestApp.zip?dl=0Flyspeck
I used this in my app and it causes a recursive call between layoutSubviews and setup causing a stack overflow error! Something is wrong hereAnglophobe
I had the extra spacing at the bottom issue when using a UITableViewCell. I found that doing the sizeThatFits part in layoutSubview froze my app (infinite loop?) if I attempted to edit the text view. Instead, I did what El Horrible suggested and that works fine.Classless
Guys take care using didMoveToSuperview - although it can seemingly work in simple cases, if you don't something in layout, it will, of course, never size properly as the layout changes! Regarding the other comment, in iOS programming it's easy to accidentally have an infinite loop in your custom classes, I certainly often do it!, it is unrelated to anything here.)Volteface
Great collation of info. Unfortunately neither of the two fixes for that extra space at the bottom works for me. When UITextView contains 1 line, there's no extra bottom padding. When it contains 2 or 3 lines, there is. Using UITextViewFixed and applying all fixes you've suggested, testing both with & without the two optional ones.Temperamental
When I try your first fix for extra bottom space, the height remains the same, but text is incorrectly offset in the Y axis, as though top inset padding is unzeroed. The only working solution I've found for a tight-fitting UITextView that has dynamic self-sizing is to override intrinsicContentSize and fudge-adjust the calculated height by subtracting 0.4 * font.lineHeight, in the case where there's more than one visible line.Temperamental
As mentioned in other comments, #1 in "Some further issues" may lead to recursive call. Instead of trying to fix that extra space at the bottom in the layoutSubviews(), we should note that this behaviour is not at all unexpected. It happens because the layout engine first calculates the height before layoutSubviews() is fired. Therefore it takes into account the original top and bottom insets and sticks with the hight afterwards. With that in mind the solution should be to make the same adjustOlivas
#1 in "Some further issues" solution may lead to recursive call. That extra space at the bottom is not at all unexpected though. It happens because the layout engine first calculates the height before layoutSubviews() is fired. Therefore it takes into account the original top and bottom insets and sticks with the height afterwards. Based on that, my solution was to make the same text container adjustments in required init?(coder aDecoder: NSCoder) as proposed in layoutSubviews(). WIth that you don't have to manipulate the bounds in layoutSubviews().Olivas
@Olivas for sure, anytime at all you use layoutSubviews you risk making a mistake and ending up in a loop and crash! It's a good idea to use required init?(coder but that would also take some thought about the layout cycle and, perhaps, you'd have to be careful if code v. storyboard bringup?Volteface
B
259

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.

Burchette answered 26/7, 2009 at 18:59 Comment(11)
I'm resorting to making the TextView's frame extend farther to the right. At about 16.0 additional points the wrapping behavior of my textview and my label match up. Not crazy about this approach.Wombat
16.0f makes sense, so I'm not too worried. I believe the UITextView underestimates its own size by 16.0f as a direct result of insetting by 8.0f on the left and right edges.Villainous
Yes it does work in iOS 7. Make sure the UIEdgeInset is set to the right values. It might be different than UIEdgeInsetsMake(-4,-8,0,0).Laughter
In IOS 7 I found UIEdgeInsetsMake(0,-4,0,-4) worked the best.Dyadic
Yes, those are example numbers. I stated "Its only a custom solution for the particular field size I was drawing". Mainly I was trying to illustrate that you have to play with the numbers for your unique layout situation.Burchette
UITextAlignmentLeft is deprecated in iOS 7. Use NSTextAlignmentLeft.Noleta
UIEdgeInsetsMake(-8,-4,0,0) works best for me. shrugLolalolande
UIEdgeInsetsMake(-8,-4,-8,-4) gets rid of the padding on all four sides. iOS 8.Operon
I don't get why people upvote an answer that utilizes hard-coded values. It is VERY likely to break in future versions of iOS and is just a plain bad idea.Sandasandakan
As can be seen from the comments the specific values for the contentInset depends on font, UIKit and potentially other factors. The correct answer is https://mcmap.net/q/73259/-how-to-lose-margin-padding-in-uitextviewBujumbura
This is a hacky solution, set the 'textContainer' lineFragmentPadding to 0 instead.Gerous
T
87

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

Trove answered 4/2, 2015 at 0:58 Comment(4)
This is clearly the best answer, especially if you need a XIB-only solutionIndividuate
Copy/paste: textContainer.lineFragmentPadding | textContainerInsetPathfinder
This is what makes a beautiful answer - easy and works well, even after 3 years :)Endmost
The people praising this answer .)) I'm sorry but this is such a terrible and lazy way of setting properties. If ever the properties change, you're gonne get runtime crashes instead of compile-time. User Defined Runtime Attributes were a terrible hack, when Apple was still trying pushing storyoboards and XIBs. Please never do this, drop all storyboards and work in code.Seicento
S
47

On iOS 5 UIEdgeInsetsMake(-8,-8,-8,-8); seems to work great.

Scoles answered 8/5, 2012 at 12:36 Comment(0)
S
44

I would definitely avoid any answers involving hard-coded values, as the actual margins may change with user font-size settings, etc.

Here is user1687195's answer, written without modifying the textContainer.lineFragmentPadding (because the documentation state this is not the intended usage).

This works great for iOS 7 and later.

self.textView.textContainerInset = UIEdgeInsetsMake(
                                      0,
                                      -self.textView.textContainer.lineFragmentPadding,
                                      0,
                                      -self.textView.textContainer.lineFragmentPadding);

This is effectively the same outcome, just a bit cleaner in that it doesn't misuse the lineFragmentPadding property.

Sandasandakan answered 24/4, 2016 at 18:36 Comment(2)
I tried another solution based on this answer and it works great. The solutions is: self.textView.textContainer.lineFragmentPadding = 0;Desiderata
@BakytAbdrasulov please read my answer. While your solution works, it is not in accordance with the docs (see the link in my answer). lineFragmentPadding isn't meant to control the margins. That's why you're supposed to use textContainerInset.Sandasandakan
S
25

Storyboard or Interface Builder solution using user-defined runtime attributes:

Screenshots are of iOS 7.1 & iOS 6.1 with contentInset = {{-10, -5}, {0, 0}}.

user-defined runtime attributes

Output

Spicule answered 5/9, 2013 at 9:2 Comment(2)
Worked for me in iOS 7.Categorical
Up just for user defined runtime attributes - awesome!Counterstamp
K
17

You can use the textContainerInset property of UITextView:

textView.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);

(top, left, bottom, right)

Kuomintang answered 5/5, 2015 at 5:24 Comment(3)
I wonder why this is not assigned as the best answer here. The textContainerInset really satisfied my needs.Mika
Updating the bottom value on the textContainerInset property isn't working for me when I want to change it to a new value—see #19423078.Falciform
@MaggiePhillips It's not the best answer because hardcoded values can't possibly be the right way to do this, and are guaranteed to fail in some cases. You need to take lineFragmentPadding into account.Sandasandakan
G
17

All these answers address the title question, but I wanted to propose some solutions for the problems presented in the body of the OP's question.

Size of Text Content

A quick way to calculate the size of the text inside the UITextView is to use the NSLayoutManager:

UITextView *textView;
CGSize textSize = [textView usedRectForTextContainer:textView.textContainer].size;

This gives the total scrollable content, which may be bigger than the UITextView's frame. I found this to be much more accurate than textView.contentSize since it actually calculates how much space the text takes up. For example, given an empty UITextView:

textView.frame.size = (width=246, height=50)
textSize = (width=10, height=16.701999999999998)
textView.contentSize = (width=246, height=33)
textView.textContainerInset = (top=8, left=0, bottom=8, right=0)

Line Height

UIFont has a property that quickly allows you to get the line height for the given font. So you can quickly find the line height of the text in your UITextView with:

UITextView *textView;
CGFloat lineHeight = textView.font.lineHeight;

Calculating Visible Text Size

Determining the amount of text that is actually visible is important for handling a "paging" effect. UITextView has a property called textContainerInset which actually is a margin between the actual UITextView.frame and the text itself. To calculate the real height of the visible frame you can perform the following calculations:

UITextView *textView;
CGFloat textViewHeight = textView.frame.size.height;
UIEdgeInsets textInsets = textView.textContainerInset;
CGFloat textHeight = textViewHeight - textInsets.top - textInsets.bottom;

Determining Paging Size

Lastly, now that you have the visible text size and the content, you can quickly determine what your offsets should be by subtracting the textHeight from the textSize:

// where n is the page number you want
CGFloat pageOffsetY = textSize - textHeight * (n - 1);
textView.contentOffset = CGPointMake(textView.contentOffset.x, pageOffsetY);

// examples
CGFloat page1Offset = 0;
CGFloat page2Offset = textSize - textHeight
CGFloat page3Offset = textSize - textHeight * 2

Using all of these methods, I didn't touch my insets and I was able to go to the caret or wherever in the text that I want.

Gadolinite answered 26/6, 2015 at 15:31 Comment(0)
V
14

Here is an updated version of Fattie's very helpful answer. It added two important lines that helped me get the layout working on iOS 10 and 11 (and probably on lower ones, too):

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

The important lines are the two translatesAutoresizingMaskIntoConstraints = <true/false> statements!

This surprisingly removes all margins in all my circumstances!

While the textView is not the first responder, it could happen that there is some strange bottom margin that could not be solved using the sizeThatFits method that is mentioned in the accepted answer.

When tapping into the textView, suddenly the strange bottom margin disappeared and everything looked like it should, but only as soon as the textView has got firstResponder.

So I read here on Stack Overflow that enabling and disabling translatesAutoresizingMaskIntoConstraints does help when setting the frame/bounds manually in between the calls.

Fortunately this not only works with frame setting, but with the two lines of setup() sandwiched between the two translatesAutoresizingMaskIntoConstraints calls!

This, for example, is very helpful when calculating the frame of a view using systemLayoutSizeFitting on a UIView. It gives back the correct size (which previously it didn't)!

As in the original answer mentioned:

Don't forget to turn off scrollEnabled in the Inspector! That solution does work properly in storyboard, as well as at runtime.

That's it, and now you're really done!

Voccola answered 14/2, 2018 at 14:37 Comment(0)
I
12

For iOS 10, the following line works for the top and bottom padding removing.

captionTextView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)
Ivar answered 12/10, 2016 at 6:38 Comment(2)
Xcode 8.2.1. still the same issue as mentioned in this answer. My solution was to edit the values as UIEdgeInsets(top: 0, left: -4.0, bottom: 0, right: -4.0).Fibrinolysin
UIEdgeInset.zero works using XCode 8.3 and iOS 10.3 SimulatorNappe
S
12

Latest Swift:

self.textView.textContainerInset = .init(top: -2, left: 0, bottom: 0, right: 0)
self.textView.textContainer.lineFragmentPadding = 0
Springs answered 24/5, 2018 at 7:54 Comment(1)
Great answer. This answer removes the additional Top inset. textView.textContainerInset = UIEdgeInsets.zero doesn't remove 2 pixels from top inset.Clarita
N
6

For Swift 4, Xcode 9

Use the following function. It can change the margin/padding of the text in UITextView:

public func UIEdgeInsetsMake(_ top: CGFloat, _ left: CGFloat, _ bottom: CGFloat, _ right: CGFloat) -> UIEdgeInsets

So in this case it is:

 self.textView?.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)
Noami answered 26/10, 2017 at 16:54 Comment(0)
R
5

For SwiftUI

If you are making your own TextView using UIViewRepresentable and want to control the padding, in your makeUIView function, simply do:

uiTextView.textContainerInset = UIEdgeInsets(top: 10, left: 18, bottom: 0, right: 18)

or whatever you want.

Reactivate answered 6/5, 2020 at 17:39 Comment(0)
R
4

Doing the inset solution I still had padding on the right side and the bottom. Also text alignment was causing issues. The only sure-fire way I found was to put the text view inside another view that is clipped to bounds.

Responsiveness answered 31/3, 2011 at 23:49 Comment(1)
What is the "inset solution"? Can you elaborate and/or add a reference? Please respond by editing (changing) your answer, not here in comments (without "Edit:", "Update:", or similar - the answer should appear as if it was written today).Constance
A
4

Here's an easy little extension that will remove Apple's default margin from every text view in your app.

Note: Interface Builder will still show the old margin, but your app will work as expected.

extension UITextView {

   open override func awakeFromNib() {
      super.awakeFromNib();
      removeMargins();
   }

   /** Removes the Apple textview margins. */
   public func removeMargins() {
      self.contentInset = UIEdgeInsetsMake(
         0, -textContainer.lineFragmentPadding,
         0, -textContainer.lineFragmentPadding);
   }
}
Alten answered 4/3, 2018 at 0:19 Comment(0)
F
2

For me (iOS 11 & Xcode 9.4.1) what worked magically was setting up textView.font property to UIFont.preferred(forTextStyle:UIFontTextStyle) style and also the first answer as mentioned by Fattie. But the Fattie answer did not work till I set the textView.font property. Else UITextView keeps behaving erratically.

Freeforall answered 17/6, 2018 at 11:20 Comment(0)
I
2

In case anyone is looking for the latest Swift version then the below code is working fine with Xcode 10.2 and Swift 4.2

yourTextView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
Isotone answered 16/5, 2019 at 10:44 Comment(0)
A
1

I have found one more approach. Getting the view with text from UITextView's subviews and setting it up in the layoutSubview method of a subclass:

- (void)layoutSubviews {
    [super layoutSubviews];

    const int textViewIndex = 1;
    UIView *textView = [self.subviews objectAtIndex:textViewIndex];
    textView.frame = CGRectMake(
                                 kStatusViewContentOffset,
                                 0.0f,
                                 self.bounds.size.width - (2.0f * kStatusViewContentOffset),
                                 self.bounds.size.height - kStatusViewContentOffset);
}
Amin answered 28/2, 2013 at 10:17 Comment(0)
F
1

The textView scrolling also affect the position of the text and make it look like not vertically centered. I managed to center the text in the view by disabling the scrolling and setting the top inset to 0:

    textView.scrollEnabled = NO;
    textView.textContainerInset = UIEdgeInsetsMake(0, textView.textContainerInset.left, textView.textContainerInset.bottom, textView.textContainerInset.right);

For some reason I haven't figured it out yet, the cursor is still not centered before the typing begins, but the text centers immediately as I start typing.

Flapjack answered 25/12, 2016 at 19:37 Comment(0)
B
1

In case you want to set an HTML string and avoid the bottom padding, please make sure that you are not using block tags, i.e., div and p.

In my case this was the reason. You can easily test it out by replacing occurrences of block tags with, e.g., the span tag.

Bisitun answered 3/1, 2020 at 17:7 Comment(0)
A
1

You need to set inset and lineFragmentPadding on didMoveToSuperView

@IBDesignable class UITextViewFixed: UITextView {
    override func didMoveToSuperview() {
        super.didMoveToSuperview()
        setup()
    }
    func setup() {
        textContainerInset = UIEdgeInsets.zero
        textContainer.lineFragmentPadding = 0
    }
}
Atlee answered 15/6, 2021 at 14:39 Comment(1)
Is it textContainerInset? Not textContainer.Inset?Constance
C
1

For anyone coming to this because of parsing HTML into UITextView, just remove the new line introduced by the closing p tag "/n". I assume that you work with NSMutableAttributedString:

if let lastCharacter = attributedString.string.last, lastCharacter == "\n" {
    attributedString.deleteCharacters(in: NSRange(location:(attributedString.length) - 1, length:1))
}
Caceres answered 23/8, 2021 at 8:14 Comment(0)
A
-4
[firstNameTextField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];
Agamemnon answered 11/8, 2011 at 21:48 Comment(1)
An explanation would be in order. E.g., what is the idea/gist? Please respond by editing (changing) your answer, not here in comments (without "Edit:", "Update:", or similar - the answer should appear as if it was written today).Constance

© 2022 - 2024 — McMap. All rights reserved.