我的iOS应用程序中有一个UITextView,用于显示大量的文本。
我通过使用UITextView的偏移边距参数来对这些文本进行分页。
问题是UITextView的填充似乎因所使用的字体大小和字体类型而异,这使得我的计算变得复杂难懂。
是否可以删除围绕UITextView内容周围的填充?
我的iOS应用程序中有一个UITextView,用于显示大量的文本。
我通过使用UITextView的偏移边距参数来对这些文本进行分页。
问题是UITextView的填充似乎因所使用的字体大小和字体类型而异,这使得我的计算变得复杂难懂。
是否可以删除围绕UITextView内容周围的填充?
针对iOS 7.0,我发现contentInset技巧不再起作用。以下是我用于消除iOS 7中边距/填充的代码。
这将把文本的左边缘移至容器的左边缘:
textView.textContainer.lineFragmentPadding = 0
这会导致文本顶部与容器顶部对齐:
textView.textContainerInset = .zero
需要使用这两行代码才能完全移除边距/内边距。
self.descriptionTextView.textContainerInset = UIEdgeInsetsZero; 本翻译保持原意,尽可能通俗易懂。 - jessepinholineFragmentPadding设置为0是我正在寻找的技巧。我不知道为什么苹果公司让UITextView内容与其他控件对齐如此困难。 - phatmannlineFragmentPadding 不应该用来修改边距。根据文档所述,行片段填充不是用来表达文本边距的。相反,您应该在文本视图上使用插图、调整段落边距属性或更改文本视图在其父视图中的位置。 - Martin BergeriOS中最傻的bug之一。
这里提供的类UITextViewFixed被广泛使用,通常是总体上最合理的解决方案。
以下是该类:
@IBDesignable class UITextViewFixed: UITextView {
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
func setup() {
textContainerInset = UIEdgeInsets.zero
textContainer.lineFragmentPadding = 0
}
}
别忘了在Inspector中关闭scrollEnabled!
该解决方案在Storyboard中正常工作。
该解决方案在运行时正常工作。
你完成了。通常情况下,这应该是你大多数需要的。
即使你正在动态更改文本视图的高度,UITextViewFixed 通常也能满足所有需求。
(一个常见的动态更改高度的示例是随着用户输入而更改高度。)
这里是苹果破损的UITextView...

这里是 UITextViewFixed:

当然,你必须注意...
(打开scrollEnabled意味着“通过尽可能扩大底部边距来尽可能垂直地扩展此视图。”)
(1) 在非常不寻常的情况下,当动态更改高度时,Apple会做一件奇怪的事情:他们在底部添加额外空间。
不,真的!这可能是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(2023年)中这个方法根本就不起作用!或许未来需要添加这行代码。
UITextView在 iOS 中的崩溃问题是移动计算领域最奇怪的事情之一。这个问题已经持续了十年,但却依然没有得到解决!
最后,这里有一个类似的小提示适用于TextField:如何在 Swift 中设置 UITextField 的最大字符长度
通常情况下,你会像使用一个 UILabel 一样使用 UITextView。所以你希望它使用省略号“...”截断文本,那么可以添加以下代码:
textContainer.lineBreakMode = .byTruncatingTail
通常你使用一个文本视图仅用于显示文本。所以,你使用lines "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比UITextView具有许多优点。UILabel不会遇到在此问答页面上描述的问题。
实际上,我们通常“放弃”并使用UITextView的原因是UILabel很难使用。特别是要正确地向UILabel添加填充非常困难。
事实上,这是一篇关于如何“最终”正确地向UILabel添加填充的完整讨论:向UILabel添加空格/填充。在某些情况下,如果您正在进行动态高度单元格的困难布局,则有时最好用UILabel做法处理。
translatesAutoresizingMaskIntoConstraints这个特殊技巧来摆脱所有插图。
仅凭这个答案,我无法使用自动布局删除视图层次结构中的所有边距(一些奇怪的底部边距无法消失)。
它还处理了使用systemLayoutSizeFitting计算视图大小的问题。由于有bug的UITextView,以前会返回无效的大小。 - Fab1n这个解决方法是在2009年iOS 3.0发布时编写的,现在已经不适用了。
我遇到了完全相同的问题,最终我不得不使用
nameField.contentInset = UIEdgeInsetsMake(-4, -8, 0, 0);
其中nameField是一个UITextView。我使用的字体是Helvetica 16点大小,这只是为特定字段大小而定制的解决方案。这使得左侧偏移与左侧齐平,并使顶部偏移在其所绘制的框中。
此外,这似乎仅适用于使用默认对齐方式的UITextView,即
nameField.textAlignment = NSTextAlignmentLeft;
例如,将其右对齐并且 UIEdgeInsetsMake 似乎根本不影响右边缘。
至少,使用 .contentInset 属性可让您以“正确”的位置放置字段,并调整偏差而无需抵消您的 UITextView。
在已有的一些好答案的基础上,这里提供了一个纯Storyboard / Interface Builder-based解决方案,适用于iOS 7.0+。
为UITextView设置以下键的用户定义的运行时属性(User Defined Runtime Attributes):
[具体内容需要根据上下文和实际情况进行翻译]
textContainer.lineFragmentPadding
textContainerInset

UIEdgeInsetsMake(-8,-8,-8,-8); 看起来非常有效。如果实际边距可能会随用户字体大小设置等变化,我肯定会避免任何涉及硬编码值的答案。
这里是user1687195的答案,未修改textContainer.lineFragmentPadding(因为文档说明这不是预期的用法)。
这对iOS 7及更高版本非常有效。
self.textView.textContainerInset = UIEdgeInsetsMake(
0,
-self.textView.textContainer.lineFragmentPadding,
0,
-self.textView.textContainer.lineFragmentPadding);
这实际上是相同的结果,只不过更加干净,因为它没有误用lineFragmentPadding属性。
self.textView.textContainer.lineFragmentPadding = 0; - Bakyt AbdrasulovlineFragmentPadding并不用于控制边距。这就是为什么您应该使用textContainerInset的原因。 - ldoogy使用用户定义的运行时属性的Storyboard或Interface Builder解决方案:
屏幕截图显示iOS 7.1和iOS 6.1,其中contentInset = {{-10,-5},{0,0}}。


所有这些答案都回答了标题问题,但我想提出一些解决方法来解决原帖中提出的问题。
计算 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;
最后,现在您已经获取了可见文本大小和内容,可以通过将textHeight从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
使用所有这些方法后,我没有触及我的插图区并且可以到达我想要的光标或文本位置。
textView.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);
(顶部、左侧、底部、右侧)
textContainerInset属性的底部值更改为新值时,更新操作并不起作用。请参见https://dev59.com/_GIk5IYBdhLWcg3wRcQo。 - Evan R这是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>!
这意外地消除了所有情况下的边距!
当textView不是第一响应者时,可能会出现一些奇怪的底部边距,这无法使用接受的答案中提到的sizeThatFits方法解决。
当点击进入textView时,突然间奇怪的底部边距消失了,一切看起来都像应该的样子,但只有在textView获得firstResponder后才能如此。
所以我在Stack Overflow上读到,启用和禁用translatesAutoresizingMaskIntoConstraints可以在调用之间手动设置框架/边界时有所帮助。
幸运的是,这不仅适用于框架设置,而且还适用于两行setup()夹在两个translatesAutoresizingMaskIntoConstraints调用之间的情况!
例如,在使用UIView上的systemLayoutSizeFitting计算视图的框架时,这非常有帮助。它返回正确的大小(先前没有)!
如原始答案中所述:
不要忘记在检查器中关闭scrollEnabled!这个解决方案在storyboard和运行时都有效。
就是这样,现在你真的完成了!