如何在UITextView失去边距/填充

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

然后,我使用UITextView的offset margin参数对该文本进行分页。

我的问题是,填充的UITextView是混淆我的计算,因为它似乎是不同的取决于我使用的字体大小和字体。

是否有可能移除围绕UITextView内容的填充?

225503 次浏览

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

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

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

其中nameField是UITextView。我使用的字体是Helvetica 16号。这只是我所绘制的特定字段大小的自定义解决方案。这使得左偏移量与左侧齐平,以及我想要它被绘制的盒子的顶部偏移量。

此外,这似乎只适用于UITextViews,其中您使用默认对齐,即,

nameField.textAlignment = NSTextAlignmentLeft;

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

至少,使用。contentinset属性允许你用“correct”来放置你的字段。位置,并适应偏差,而不抵消你的UITextViews

做插入解决方案,我仍然有填充在右侧和底部。文本对齐也会导致问题。我找到的唯一可靠的方法是将文本视图放在另一个被剪切到边界的视图中。

[firstNameTextField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];

在iOS 5上UIEdgeInsetsMake(-8,-8,-8,-8);似乎工作得很好。

我找到了另一种方法。从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);
}

故事板或接口生成器解决方案使用用户定义的运行时属性:

截图是iOS 7.1 &iOS 6.1, contentInset = \{\{-10, -5}, {0, 0}}

用户自定义运行时属性

Output

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

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

textView.textContainer.lineFragmentPadding = 0

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

textView.textContainerInset = .zero

这两行都需要完全删除空白/填充。

基于已经给出的一些好的答案,这里是一个纯基于Storyboard / Interface builder的解决方案,适用于iOS 7.0+

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

textContainer.lineFragmentPadding
textContainerInset

Interface Builder

你可以使用UITextViewtextContainerInset属性:

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

(上,左,下,右)

所有这些答案都针对标题问题,但我想对OP问题主体中提出的问题提出一些解决方案。

文本内容大小

一个快速计算UITextView内文本大小的方法是使用NSLayoutManager:

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

这给出了总的可滚动内容,它可能比UITextView的帧大。我发现这比textView.contentSize更准确,因为它实际上计算了文本占用的空间。例如,给定一个空的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)

行高

UIFont有一个属性,可以快速获取给定字体的行高。所以你可以快速找到你的UITextView文本的行高:

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

计算可见文本大小

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

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

确定分页大小

最后,现在你有了可见文本大小和内容,你可以通过从textSize减去textHeight来快速确定你的偏移量:

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

使用所有这些方法,我不需要触摸我的嵌入,我可以去插入号或文本中任何我想要的地方。

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

这里是user1687195的回答,没有修改textContainer.lineFragmentPadding(因为文档表示这不是预期的用法)。

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

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

这实际上是相同的结果,只是稍微干净一点,因为它没有滥用lineFragmentPadding属性。

对于iOS 10,下面这行代码用于移除顶部和底部填充。

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

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

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

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

2021年最新情况

这是iOS系统中最愚蠢的漏洞之一。

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

下面是这个类:

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

不要忘记在检查器中关闭scrollEnabled !

  1. 解决方案在故事板中正常工作

  2. 解决方案运行时正常

就这样,你完成了。

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

即使您正在更改文本视图忙碌地的高度,UITextViewFixed通常也可以满足您的所有需要。

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

这是来自苹果的破碎的UITextView…

 Interface Builder with UITextView截图

这是UITextViewFixed:

 Interface Builder with UITextViewFixed截图

请注意,当然你必须……

...在检查器中关闭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)在极少数情况下,为了解决苹果的另一个微妙混乱,你必须补充:

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

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

contentInset = UIEdgeInsets.zero

UITextViewFixed.lineFragmentPadding = 0后面。

然而……信不信由你……当前iOS中的just doesn't work !(检查2021。)将来可能有必要添加这一行。

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

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

完全随机提示:如何添加"…在最后

通常你会像使用uilabel一样使用UITextView。因此,您希望它使用省略号来截断文本,"…

如果是,请补充:

 textContainer.lineBreakMode = .byTruncatingTail

方便的提示,如果你想要高度,当,有没有文本

通常使用文本视图只显示文本。所以,你用“;0;;”也就是说,文本视图会根据文本行数自动改变高度。

太好了。但是如果根本没有文字,然后不幸的是你得到相同的高度就好像只有一行文字!!!!文本视图永远不会“消失”。

Enter image description here

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

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

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

那UILabel呢?

当只显示文本时,UILabel比UITextView有很多优势。UILabel不会遭受这个问答页面上描述的问题。

的确,我们所有人通常“放弃”的原因;只使用UITextView是因为UILabel很难使用。特别是,正确地将填充添加到UILabel中是非常困难的。

事实上,这里有一个关于如何“最终”的完整讨论。正确地添加填充UILabel: 为UILabel添加空格/填充。在某些情况下,如果你正在用动态高度单元格做一个困难的布局,有时候用UILabel来做会更好。

对于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)
这是胖子的回答很有帮助的更新版本。 它添加了两行重要的行,帮助我在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>语句!

在我所有的情况下,这个删除所有边距令人惊讶!

虽然textView不是第一个响应器,但可能会出现一些奇怪的下边界,无法使用接受的答案中提到的sizeThatFits方法解决。

当点击进入textView,突然奇怪的底部边缘消失了,一切看起来像它应该,但只要textView有firstResponder

所以我读了这里是Stack Overflow,启用和禁用translatesAutoresizingMaskIntoConstraints确实有助于在调用之间手动设置帧/边界。

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

例如,当计算视图在#EYZ1上使用systemLayoutSizeFitting的帧时,它是很有帮助。它会返回正确的大小(以前它没有)!

如原文所述:

不要忘记在检查器中关闭scrollEnabled !这个解决方案在故事板和运行时都能正常工作。

就是这样,现在你really done!

这是一个简单的小扩展,将删除苹果的默认边际从每个文本视图在你的应用程序。

注意:界面构建器仍将显示旧的边缘,但你的应用程序将按预期工作。

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);
}
}

最新迅速:

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

对我来说(iOS 11 &Xcode 9.4.1)的神奇之处在于设置textView。字体属性设置为UIFont.preferred(forTextStyle:UIFontTextStyle)样式,第一个答案设置为胖子提到。但胖子的答案没有工作,直到我设置textView。字体属性。否则UITextView行为不稳定。

如果有人正在寻找最新的Swift版本,那么下面的代码可以很好地使用Xcode 10.2斯威夫特4.2

yourTextView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

如果你想设置一个HTML字符串并避免底部填充,请确保你没有使用块标记,即divp

对我来说,这就是原因。您可以通过使用span标记替换出现的块标记来轻松地测试它。

对于SwiftUI

如果你正在使用UIViewRepresentable制作自己的TextView并想要控制填充,在你的makeUIView函数中,简单地做:

# EYZ0

或者随便你。

你需要在didMoveToSuperView上设置inset和lineFragmentPadding

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

对于那些将HTML解析为UITextView的人来说,只需删除由结束p标记"/n"引入的新行。我假设您使用NSMutableAttributedString:

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