提问人:sniurkst 提问时间:4/14/2009 最后编辑:Peter Mortensensniurkst 更新时间:2/21/2023 访问量:232154
如何在UITextView中丢失边距/填充
How to lose margin/padding in UITextView
问:
我的 iOS 应用程序中有一个显示大量文本的应用程序。UITextView
然后,我使用 .UITextView
我的问题是 的填充混淆了我的计算,因为它似乎因我使用的字体大小和字体而异。UITextView
是否可以删除内容周围的填充物?UITextView
答:
此解决方法是在 2009 年 iOS 3.0 发布时编写的。它不再适用。
我遇到了完全相同的问题,最后我不得不使用
nameField.contentInset = UIEdgeInsetsMake(-4, -8, 0, 0);
其中 nameField 是 .我碰巧使用的字体是 Helvetica 16 点。它只是针对我正在绘制的特定字段大小的自定义解决方案。这使得左侧偏移量与左侧齐平,并且顶部偏移量与我想要它绘制的框齐平。UITextView
此外,这似乎仅适用于您使用默认资格的地方,即UITextViews
nameField.textAlignment = NSTextAlignmentLeft;
例如,向右对齐,似乎对右边缘完全没有影响。UIEdgeInsetsMake
至少,使用 .contentInset 属性允许您将字段放置在“正确”的位置,并在不抵消 .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);
}
使用用户定义的运行时属性的 Storyboard 或 Interface Builder 解决方案:
屏幕截图是 iOS 7.1 和 iOS 6.1 的屏幕截图。contentInset = {{-10, -5}, {0, 0}}
评论
对于 iOS 7.0,我发现 contentInset 技巧不再有效。这是我用来在 iOS 7 中去除边距/填充的代码。
这会将文本的左边缘带到容器的左边缘:
textView.textContainer.lineFragmentPadding = 0
这会导致文本的顶部与容器的顶部对齐:
textView.textContainerInset = .zero
需要两条线才能完全删除边距/填充。
评论
self.descriptionTextView.textContainerInset = UIEdgeInsetsZero;
lineFragmentPadding
lineFragmentPadding
无意修改边距。来自文档 行片段填充不是为表示文本边距而设计的。相反,您应该在文本视图上使用插图,调整段落边距属性,或更改文本视图在其上视图中的位置。
基于已经给出的一些好的答案,这里有一个纯粹的基于故事板/界面构建器的解决方案,适用于 iOS 7.0+
为以下键设置 UITextView 的用户定义的运行时属性:
textContainer.lineFragmentPadding
textContainerInset
评论
您可以使用以下属性:textContainerInset
UITextView
textView.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);
(上、左、下、右)
评论
textContainerInset
所有这些答案都解决了标题问题,但我想为 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;
确定分页大小
最后,现在您已经有了可见的文本大小和内容,您可以通过从 :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
使用所有这些方法,我没有触摸我的插图,我能够转到插入符号或文本中我想要的任何地方。
我肯定会避免任何涉及硬编码值的答案,因为实际边距可能会随着用户字体大小设置等而变化。
这是 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;
lineFragmentPadding
textContainerInset
对于 iOS 10,以下行适用于顶部和底部填充删除。
captionTextView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)
评论
UIEdgeInset.zero
使用 XCode 8.3 和 iOS 10.3 模拟器
textView 滚动还会影响文本的位置,使其看起来不是垂直居中。我设法通过禁用滚动并将顶部插图设置为 0 来使文本在视图中居中:
textView.scrollEnabled = NO;
textView.textContainerInset = UIEdgeInsetsMake(0, textView.textContainerInset.left, textView.textContainerInset.bottom, textView.textContainerInset.right);
由于某种原因我还没有弄清楚,在打字开始之前,光标仍然没有居中,但当我开始打字时,文本会立即居中。
2023 年最新
这是iOS中最愚蠢的错误之一。
这里给出的类被广泛使用,通常是总体上最合理的解决方案。UITextViewFixed
这是课程:
@IBDesignable class UITextViewFixed: UITextView {
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
func setup() {
textContainerInset = UIEdgeInsets.zero
textContainer.lineFragmentPadding = 0
}
}
别忘了在 Inspector 中关闭 scrollEnabled!
该解决方案在情节提要中正常工作
该解决方案在运行时正常工作
大功告成。一般来说,在大多数情况下,这应该是您所需要的。
即使您正在动态更改文本视图的高度,通常也可以完成您所需要的一切。UITextViewFixed
(动态更改高度的一个常见示例是随着用户键入而更改高度。
这是来自 Apple 的损坏的 UITextView......
这里是:UITextViewFixed
请注意,您当然必须...
...在 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 = 0
UITextViewFixed
然而,信不信由你,这在当前的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 进行困难的布局。
评论
translatesAutoresizingMaskIntoConstraints
systemLayoutSizeFitting
UITextView
适用于 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)
这是 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
计算视图的帧时,这非常有用。它返回正确的尺寸(以前没有)!
如原来回答中提到的:
别忘了在 Inspector 中关闭 scrollEnabled!该解决方案在情节提要和运行时都能正常工作。
就是这样,现在你真的完成了!
这里有一个简单的小扩展,它将从您应用程序的每个文本视图中删除 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);
}
}
最新 Swift:
self.textView.textContainerInset = .init(top: -2, left: 0, bottom: 0, right: 0)
self.textView.textContainer.lineFragmentPadding = 0
评论
对我来说(iOS 11 和 Xcode 9.4.1),神奇的是将 textView.font 属性设置为样式,也是 Fattie 提到的第一个答案。但是在我设置 textView.font 属性之前,Fattie 的答案不起作用。否则,UITextView 的行为一直不稳定。UIFont.preferred(forTextStyle:UIFontTextStyle)
如果有人正在寻找最新的 Swift 版本,那么以下代码适用于 Xcode 10.2 和 Swift 4.2
yourTextView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
如果您想设置 HTML 字符串并避免底部填充,请确保您没有使用块标签,即 和 .div
p
就我而言,这就是原因。您可以通过将出现的块标签替换为标签(例如标签)来轻松测试它。span
对于 SwiftUI
如果你正在使用自己的 TextView 来制作自己的 TextView 并希望控制填充,那么在你的函数中,只需执行以下操作:UIViewRepresentable
makeUIView
uiTextView.textContainerInset = UIEdgeInsets(top: 10, left: 18, bottom: 0, right: 18)
或任何你想要的东西。
您需要在 didMoveToSuperView 上设置 inset 和 lineFragmentPadding
@IBDesignable class UITextViewFixed: UITextView {
override func didMoveToSuperview() {
super.didMoveToSuperview()
setup()
}
func setup() {
textContainerInset = UIEdgeInsets.zero
textContainer.lineFragmentPadding = 0
}
}
评论
textContainerInset
textContainer.Inset
对于由于将 HTML 解析为 UITextView 而遇到此问题的人,只需删除由结束 p 标记“/n”引入的新行即可。我假设您使用 NSMutableAttributedString:
if let lastCharacter = attributedString.string.last, lastCharacter == "\n" {
attributedString.deleteCharacters(in: NSRange(location:(attributedString.length) - 1, length:1))
}
下一个:在 iOS 模拟器中截取屏幕截图
评论