如何在UITextView中丢失边距/填充

How to lose margin/padding in UITextView

提问人:sniurkst 提问时间:4/14/2009 最后编辑:Peter Mortensensniurkst 更新时间:2/21/2023 访问量:232154

问:

我的 iOS 应用程序中有一个显示大量文本的应用程序。UITextView

然后,我使用 .UITextView

我的问题是 的填充混淆了我的计算,因为它似乎因我使用的字体大小和字体而异。UITextView

是否可以删除内容周围的填充物?UITextView

iOS iPhone Cocoa-Touch UIKIT UITtextView

评论

11赞 Fattie 3/28/2017
请注意,这个 QA 已经快十年了!拥有 100,000+ 次观看,因为它是 iOS 中最愚蠢的问题之一。只是 FTR 我在下面输入了当前 2017 年相当简单/通常/公认的解决方案作为答案。
1赞 Michael 5/19/2017
我仍然得到这方面的更新,在 2009 年 IOS 3.0 刚刚发布时编写了一个硬编码的解决方法。我只是编辑了答案,以明确指出它已经过时多年,并忽略了接受的状态。
1赞 Fattie 2/26/2021
令人惊讶的是,十多年来(这个QA的200,000次浏览!)和Apple仍然没有修复UITextView中普通错误,软错误和奇怪行为的组合。这真的是所有移动计算开发中最奇怪的事情之一!

答:

259赞 Michael 7/27/2009 #1

此解决方法是在 2009 年 iOS 3.0 发布时编写的。它不再适用。

我遇到了完全相同的问题,最后我不得不使用

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

其中 nameField 是 .我碰巧使用的字体是 Helvetica 16 点。它只是针对我正在绘制的特定字段大小的自定义解决方案。这使得左侧偏移量与左侧齐平,并且顶部偏移量与我想要它绘制的框齐平。UITextView

此外,这似乎仅适用于您使用默认资格的地方,即UITextViews

nameField.textAlignment = NSTextAlignmentLeft;

例如,向右对齐,似乎对右边缘完全没有影响。UIEdgeInsetsMake

至少,使用 .contentInset 属性允许您将字段放置在“正确”的位置,并在不抵消 .UITextViews

评论

1赞 app_ 9/18/2013
是的,它确实适用于 iOS 7。确保 UIEdgeInset 设置为正确的值。它可能不同于 UIEdgeInsetsMake(-4,-8,0,0)。
15赞 Racura 10/11/2013
在IOS 7中,我发现UIEdgeInsetsMake(0,-4,0,-4)效果最好。
2赞 Michael 10/19/2013
是的,这些是示例数字。我说“它只是针对我绘制的特定字段大小的自定义解决方案”。主要是我试图说明,你必须根据你独特的布局情况来玩数字。
3赞 Jordi Kroon 3/12/2014
UITextAlignmentLeft 在 iOS 7 中已弃用。使用 NSTextAlignmentLeft。
7赞 ldoogy 8/13/2016
我不明白为什么人们会投票支持使用硬编码值的答案。它很可能会在未来版本的 iOS 中中断,这只是一个普通的坏主意。
4赞 rp90 4/1/2011 #2

在做嵌入式解决方案时,我的右侧和底部仍然有填充物。此外,文本对齐也导致了问题。我发现的唯一可靠方法是将文本视图放在另一个被裁剪到边界的视图中。

评论

0赞 Peter Mortensen 9/25/2021
什么是“嵌入式解决方案”?你能详细说明和/或添加一个参考吗?请通过编辑(更改)您的答案来回复,而不是在评论中(没有“编辑:”,“更新:”或类似内容 - 答案应该看起来像今天写的一样)。
-4赞 to0nice4eva 8/12/2011 #3
[firstNameTextField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];

评论

0赞 Peter Mortensen 9/25/2021
解释是有序的。例如,想法/要点是什么?请通过编辑(更改)您的答案来回复,而不是在评论中(没有“编辑:”,“更新:”或类似内容 - 答案应该看起来像今天写的一样)。
47赞 Engin Kurutepe 5/8/2012 #4

在 iOS 5 上似乎效果很好。UIEdgeInsetsMake(-8,-8,-8,-8);

1赞 Nikita 2/28/2013 #5

我找到了另一种方法。从 UITextView 的子视图中获取带有文本的视图,并在子类的 layoutSubview 方法中设置它:

- (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);
}
25赞 Uladzimir 9/5/2013 #6

使用用户定义的运行时属性的 Storyboard 或 Interface Builder 解决方案:

屏幕截图是 iOS 7.1 和 iOS 6.1 的屏幕截图。contentInset = {{-10, -5}, {0, 0}}

用户定义的运行时属性

输出

评论

1赞 kubilay 3/31/2014
在 iOS 7 中为我工作。
0赞 hris.to 12/15/2014
只针对用户定义的运行时属性 - 太棒了!
844赞 user1687195 9/25/2013 #7

对于 iOS 7.0,我发现 contentInset 技巧不再有效。这是我用来在 iOS 7 中去除边距/填充的代码。

这会将文本的左边缘带到容器的左边缘:

textView.textContainer.lineFragmentPadding = 0

这会导致文本的顶部与容器的顶部对齐:

textView.textContainerInset = .zero

需要两条线才能完全删除边距/填充。

评论

2赞 livings124 10/3/2013
这对我有用两行,但是当我到达 5 行时,文本被切断了。
7赞 jessepinho 11/23/2013
请注意,第二行也可以写成:self.descriptionTextView.textContainerInset = UIEdgeInsetsZero;
21赞 phatmann 10/10/2014
设置为 0 是我一直在寻找的魔力。我不知道为什么 Apple 很难将 UITextView 内容与其他控件对齐。lineFragmentPadding
3赞 Michael 6/19/2015
这是正确答案。此解决方案不会发生水平滚动。
2赞 Martin Berger 4/5/2016
lineFragmentPadding无意修改边距。来自文档 行片段填充不是为表示文本边距而设计的。相反,您应该在文本视图上使用插图,调整段落边距属性,或更改文本视图在其上视图中的位置。
87赞 dvs 2/4/2015 #8

基于已经给出的一些好的答案,这里有一个纯粹的基于故事板/界面构建器的解决方案,适用于 iOS 7.0+

为以下键设置 UITextView 的用户定义的运行时属性

textContainer.lineFragmentPadding
textContainerInset

界面生成器

评论

5赞 yano 9/17/2015
这显然是最好的答案,特别是如果你需要一个只有XIB的解决方案
17赞 Luca De Angelis 3/2/2016
复制/粘贴:textContainer.lineFragmentPadding |textContainerInset (文本容器内嵌)
3赞 Shai Mishali 2/2/2018
这就是一个美丽的答案的原因 - 即使在 3 年后也很容易且效果很好:)
0赞 Zoltán 11/13/2023
人们称赞这个答案。对不起,这是一种如此糟糕和懒惰的属性设置方式。如果属性发生更改,您将在运行时崩溃,而不是在编译时崩溃。用户定义的运行时属性是一个可怕的黑客攻击,当时苹果还在尝试推动故事板和 XIB。请不要这样做,删除所有故事板并在代码中工作。
17赞 Himanshu padia 5/5/2015 #9

您可以使用以下属性:textContainerInsetUITextView

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

(上、左、下、右)

评论

1赞 EvenBoy 4/26/2016
我想知道为什么这里没有将其指定为最佳答案。textContainerInset 确实满足了我的需求。
0赞 Evan R 4/29/2016
当我想将属性的底部值更改为新值时,更新属性的底部值对我不起作用 - 请参阅 stackoverflow.com/questions/19422578/...textContainerInset
0赞 ldoogy 5/21/2016
@MaggiePhillips 这不是最好的答案,因为硬编码值不可能是正确的方法,并且在某些情况下肯定会失败。您需要考虑 lineFragmentPadding 。
17赞 mikeho 6/26/2015 #10

所有这些答案都解决了标题问题,但我想为 OP 问题正文中提出的问题提出一些解决方案。

文本内容的大小

计算 内部文本大小的快速方法是使用 :UITextViewNSLayoutManager

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

这提供了总的可滚动内容,该内容可能大于 的帧。我发现这比它实际上计算文本占用了多少空间要准确得多。例如,给定一个空的:UITextViewtextView.contentSizeUITextView

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)

行高

UIFont具有一个属性,可让您快速获取给定字体的行高。因此,您可以快速找到以下文本的行高:UITextView

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

计算可见文本大小

确定实际可见的文本量对于处理“分页”效果非常重要。 有一个属性,它实际上是实际和文本本身之间的边距。要计算可见帧的实际高度,可以执行以下计算:UITextViewtextContainerInsetUITextView.frame

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

确定分页大小

最后,现在您已经有了可见的文本大小和内容,您可以通过从 :textHeighttextSize

// 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

使用所有这些方法,我没有触摸我的插图,我能够转到插入符号或文本中我想要的任何地方。

44赞 ldoogy 4/25/2016 #11

我肯定会避免任何涉及硬编码值的答案,因为实际边距可能会随着用户字体大小设置等而变化。

这是 user1687195 的答案,在未修改的情况下编写(因为文档指出这不是预期用法)。textContainer.lineFragmentPadding

这适用于 iOS 7 及更高版本。

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

这实际上是相同的结果,只是更简洁一些,因为它不会滥用 lineFragmentPadding 属性。

评论

1赞 Bakyt Abdrasulov 1/23/2017
我根据这个答案尝试了另一种解决方案,效果很好。解决方案是:self.textView.textContainer.lineFragmentPadding = 0;
1赞 ldoogy 1/23/2017
@BakytAbdrasulov请阅读我的回答。虽然您的解决方案有效,但它不符合文档(请参阅我的答案中的链接)。 并不意味着控制利润率。这就是为什么你应该使用 .lineFragmentPaddingtextContainerInset
12赞 Anson Yao 10/12/2016 #12

对于 iOS 10,以下行适用于顶部和底部填充删除。

captionTextView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)

评论

0赞 Darkwonder 2/25/2017
Xcode 8.2.1 格式。仍然是本答案中提到的相同问题。我的解决方案是将值编辑为 UIEdgeInsets(顶部:0,左侧:-4.0,底部:0,右侧:-4.0)。
0赞 Simon Warta 3/31/2017
UIEdgeInset.zero使用 XCode 8.3 和 iOS 10.3 模拟器
1赞 Alex 12/26/2016 #13

textView 滚动还会影响文本的位置,使其看起来不是垂直居中。我设法通过禁用滚动并将顶部插图设置为 0 来使文本在视图中居中:

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

由于某种原因我还没有弄清楚,在打字开始之前,光标仍然没有居中,但当我开始打字时,文本会立即居中。

490赞 Fattie 2/20/2017 #14

2023 年最新

这是iOS中最愚蠢的错误之一。

这里给出的类被广泛使用,通常是总体上最合理的解决方案UITextViewFixed

这是课程:

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

别忘了在 Inspector 中关闭 scrollEnabled!

  1. 该解决方案在情节提要中正常工作

  2. 该解决方案在运行时正常工作

大功告成。一般来说,在大多数情况下,这应该是您所需要的

即使您正在动态更改文本视图的高度,通常也可以完成您所需要的一切。UITextViewFixed

(动态更改高度的一个常见示例是随着用户键入而更改高度。

这是来自 Apple 的损坏的 UITextView......

使用 UITextView 的 Interface Builder 的屏幕截图

这里是:UITextViewFixed

带有 UITextViewFixed 的 Interface Builder 的屏幕截图

请注意,您当然必须...

...在 Inspector 中关闭 scrollEnabled!

(启用 scrollEnabled 意味着“通过尽可能扩展下边距,使此视图尽可能垂直扩展”。


其他一些问题

(1)在非常不寻常的情况下,当动态改变高度时,苹果会做一件奇怪的事情:它们在底部增加了额外的空间

不,真的!这一定是iOS中最令人愤怒的事情之一。

如果您遇到问题,这里有一个“快速修复”,通常会有所帮助:

...
        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)在极少数情况下,要修复Apple的另一个微妙的混乱,您必须添加:

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

(3)可以说,我们应该补充:

contentInset = UIEdgeInsets.zero

就在..lineFragmentPadding = 0UITextViewFixed

然而,信不信由你,这在当前的iOS中是行不通的!(2023 年检查。将来可能需要添加该行。

在iOS中被打破的事实是所有移动计算中最奇怪的事情之一。这个问题十周年了,它仍然没有解决!UITextView

最后,这里有一个与文本字段有点类似的提示:在 Swift 中设置 UITextField 的最大字符长度

完全随机的提示:如何添加“...”在最后

通常,您使用的 UITextView“就像 UILabel”一样。因此,您希望它使用省略号“...”截断文本

如果是这样,请添加:

 textContainer.lineBreakMode = .byTruncatingTail

如果你想要高度,那么这是一个方便的提示,当根本没有文字时

通常,使用文本视图仅显示文本。因此,您使用行“0”表示文本视图将根据文本行数自动更改高度。

真棒。但是,如果根本没有文本,那么不幸的是,您得到的高度与只有一行文本相同!!!文本视图永远不会“消失”。

在此处输入图像描述

如果你想让它“消失”,只需添加这个

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
}

在此处输入图像描述

(我把它设为“1”高度,所以很清楚那个演示中发生了什么,“0”就可以了。

UILabel呢?

当只显示文本时,UILabel 比 UITextView 具有许多优势。UILabel不会受到此问答页面上描述的问题的影响。

事实上,我们通常“放弃”而只使用 UITextView 的原因是 UILabel 很难使用。特别是,正确地向 UILabel 添加填充是非常困难的。

事实上,这里是关于如何“最终”正确地向 UILabel 添加填充的完整讨论:向 UILabel 添加空格/填充。在某些情况下,如果您使用动态高度单元格进行困难的布局,有时最好使用 UILabel 进行困难的布局。

评论

1赞 shim 7/14/2017
为什么说有必要在检查器中关闭 scrollEnabled?当我这样做时,我的文本视图中的空间似乎更少。
1赞 shim 7/14/2017
滚动视图的框架高度与内容大小无关。OP提出的问题没有提到身高。只是试图理解你的反应。
3赞 Fab1n 2/14/2018
我已经发布了对@Fattie答案的更新答案,它帮助我使用启用/禁用的特殊技巧真正摆脱了所有插图。仅凭这个答案,我无法使用自动布局删除视图层次结构中的所有边距(一些奇怪的下边距拒绝消失)。它还使用 处理视图大小的计算,该视图大小以前由于错误而返回无效大小translatesAutoresizingMaskIntoConstraintssystemLayoutSizeFittingUITextView
2赞 El Horrible 7/17/2019
我在表格中有文本视图,那些有单行文本的视图没有正确计算其高度(自动布局)。为了修复它,我不得不覆盖didMoveToSuperview并在那里调用setup。
4赞 Tometoyou 10/4/2019
我在我的应用程序中使用了它,它会导致 layoutSubviews 和 setup 之间的递归调用,从而导致堆栈溢出错误!这里出了点问题
6赞 Shan Ye 10/27/2017 #15

适用于 Swift 4、Xcode 9

使用以下函数。它可以更改 UITextView 中文本的边距/填充:

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

所以在这种情况下,它是:

 self.textView?.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)
14赞 Fab1n 2/14/2018 #16

这是 Fattie 非常有用的答案的更新版本。 它添加了两行重要的行,帮助我在 iOS 10 和 11 上(也可能在较低的版本)上工作布局:

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

重要的行是两个 translatesAutoresizingMaskIntoConstraints = <true/false> 语句!

令人惊讶的是,这消除了我所有情况下的所有边距

虽然不是第一响应者,但可能会发生一些奇怪的底部边缘,无法使用已接受的答案中提到的方法解决。textViewsizeThatFits

当点击textView时,突然间奇怪的下边距消失了,一切看起来都像它应该的那样,但只有在textView得到.firstResponder

所以我在 Stack Overflow 上读到,在调用之间手动设置帧/边界时,启用和禁用确实有帮助。translatesAutoresizingMaskIntoConstraints

幸运的是,这不仅适用于帧设置,而且适用于夹在两个调用之间的两行!setup()translatesAutoresizingMaskIntoConstraints

例如,在 UIView 上使用 systemLayoutSizeFitting 计算视图的帧时,这非常有用。它返回正确的尺寸(以前没有)!

如原来回答中提到的:

别忘了在 Inspector 中关闭 scrollEnabled!该解决方案在情节提要和运行时都能正常工作。

就是这样,现在你真的完成了!

4赞 N S 3/4/2018 #17

这里有一个简单的小扩展,它将从您应用程序的每个文本视图中删除 Apple 的默认边距。

注意:Interface Builder 仍将显示旧的边距,但您的应用将按预期工作。

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);
   }
}
12赞 Urvish Modi 5/24/2018 #18

最新 Swift:

self.textView.textContainerInset = .init(top: -2, left: 0, bottom: 0, right: 0)
self.textView.textContainer.lineFragmentPadding = 0

评论

0赞 korgx9 8/2/2019
很好的答案。此答案删除了额外的顶部插图。textView.textContainerInset = UIEdgeInsets.zero 不会从顶部插图中删除 2 个像素。
2赞 Nikhil Pandey 6/17/2018 #19

对我来说(iOS 11 和 Xcode 9.4.1),神奇的是将 textView.font 属性设置为样式,也是 Fattie 提到的第一个答案。但是在我设置 textView.font 属性之前,Fattie 的答案不起作用。否则,UITextView 的行为一直不稳定。UIFont.preferred(forTextStyle:UIFontTextStyle)

2赞 Bhavin_m 5/16/2019 #20

如果有人正在寻找最新的 Swift 版本,那么以下代码适用于 Xcode 10.2Swift 4.2

yourTextView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
1赞 Dawid Płatek 1/4/2020 #21

如果您想设置 HTML 字符串并避免底部填充,请确保您没有使用块标签,即 和 .divp

就我而言,这就是原因。您可以通过将出现的块标签替换为标签(例如标签)来轻松测试它。span

5赞 P. Ent 5/7/2020 #22

对于 SwiftUI

如果你正在使用自己的 TextView 来制作自己的 TextView 并希望控制填充,那么在你的函数中,只需执行以下操作:UIViewRepresentablemakeUIView

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

或任何你想要的东西。

1赞 mert kutluca 6/15/2021 #23

您需要在 didMoveToSuperView 上设置 inset 和 lineFragmentPadding

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

评论

0赞 Peter Mortensen 11/7/2021
是吗?不?textContainerInsettextContainer.Inset
1赞 christostsang 8/23/2021 #24

对于由于将 HTML 解析为 UITextView 而遇到此问题的人,只需删除由结束 p 标记“/n”引入的新行即可。我假设您使用 NSMutableAttributedString

if let lastCharacter = attributedString.string.last, lastCharacter == "\n" {
    attributedString.deleteCharacters(in: NSRange(location:(attributedString.length) - 1, length:1))
}