ios 使用自动布局扩展为文本的 UITextView
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16868117/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UITextView that expands to text using auto layout
提问by tharris
I have a view that is laid out completely using auto layout programmatically. I have a UITextView in the middle of the view with items above and below it. Everything works fine, but I want to be able to expand UITextView as text is added. This should push everything below it down as it expands.
我有一个以编程方式完全使用自动布局布局的视图。我在视图中间有一个 UITextView,上面和下面都有项目。一切正常,但我希望能够在添加文本时扩展 UITextView。随着它的扩展,这应该会将其下方的所有内容向下推。
I know how to do this the "springs and struts" way, but is there an auto layout way of doing this? The only way I can think of is by removing and re-adding the constraint every time it needs to grow.
我知道如何以“弹簧和支柱”的方式做到这一点,但是有没有一种自动布局的方式来做到这一点?我能想到的唯一方法是在每次需要增长时删除并重新添加约束。
采纳答案by ancajic
The view containing UITextView will be assigned its size with setBounds
by AutoLayout. So, this is what I did. The superview is initially set up all the other constraints as they should be, and in the end I put one special constraint for UITextView's height, and I saved it in an instance variable.
包含 UITextView 的视图将setBounds
通过 AutoLayout分配其大小。所以,这就是我所做的。超级视图最初设置了所有其他约束,最后我为 UITextView 的高度设置了一个特殊约束,并将其保存在一个实例变量中。
_descriptionHeightConstraint = [NSLayoutConstraint constraintWithItem:_descriptionTextView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:0.f
constant:100];
[self addConstraint:_descriptionHeightConstraint];
In the setBounds
method, I then changed the value of the constant.
在setBounds
方法中,我然后更改了常量的值。
-(void) setBounds:(CGRect)bounds
{
[super setBounds:bounds];
_descriptionTextView.frame = bounds;
CGSize descriptionSize = _descriptionTextView.contentSize;
[_descriptionHeightConstraint setConstant:descriptionSize.height];
[self layoutIfNeeded];
}
回答by vitaminwater
Summary: Disable scrolling of your text view, and don't constraint its height.
总结:禁用文本视图的滚动,并且不限制其高度。
To do this programmatically, put the following code in viewDidLoad
:
要以编程方式执行此操作,请将以下代码放入viewDidLoad
:
let textView = UITextView(frame: .zero, textContainer: nil)
textView.backgroundColor = .yellow // visual debugging
textView.isScrollEnabled = false // causes expanding height
view.addSubview(textView)
// Auto Layout
textView.translatesAutoresizingMaskIntoConstraints = false
let safeArea = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
textView.topAnchor.constraint(equalTo: safeArea.topAnchor),
textView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor)
])
To do this in Interface Builder, select the text view, uncheck Scrolling Enabled in the Attributes Inspector, and add the constraints manually.
要在 Interface Builder 中执行此操作,请选择文本视图,在 Attributes Inspector 中取消选中 Scrolling Enabled,然后手动添加约束。
Note: If you have other view/s above/below your text view, consider using a UIStackView
to arrange them all.
注意:如果您的文本视图上方/下方有其他视图,请考虑使用 aUIStackView
来排列它们。
回答by Michael Revlis
Here's a solution for people who prefer to do it all by auto layout:
对于喜欢通过自动布局完成所有工作的人来说,这是一个解决方案:
In Size Inspector:
在尺寸检查器中:
Set content compression resistance priority vertical to 1000.
Lower the priority of constraint height by click "Edit" in Constraints. Just make it less than 1000.
将内容压缩阻力优先级垂直设置为 1000。
通过在约束中单击“编辑”来降低约束高度的优先级。让它小于1000。
In Attributes Inspector:
在属性检查器中:
- Uncheck "Scrolling Enabled"
- 取消选中“启用滚动”
回答by dhallman
UITextView doesn't provide an intrinsicContentSize, so you need to subclass it and provide one. To make it grow automatically, invalidate the intrinsicContentSize in layoutSubviews. If you use anything other than the default contentInset (which I do not recommend), you may need to adjust the intrinsicContentSize calculation.
UITextView 不提供内在内容大小,因此您需要对其进行子类化并提供一个。要使其自动增长,请使 layoutSubviews 中的内在内容大小无效。如果您使用默认 contentInset 以外的任何内容(我不推荐),您可能需要调整 internalContentSize 计算。
@interface AutoTextView : UITextView
@end
#import "AutoTextView.h"
@implementation AutoTextView
- (void) layoutSubviews
{
[super layoutSubviews];
if (!CGSizeEqualToSize(self.bounds.size, [self intrinsicContentSize])) {
[self invalidateIntrinsicContentSize];
}
}
- (CGSize)intrinsicContentSize
{
CGSize intrinsicContentSize = self.contentSize;
// iOS 7.0+
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0f) {
intrinsicContentSize.width += (self.textContainerInset.left + self.textContainerInset.right ) / 2.0f;
intrinsicContentSize.height += (self.textContainerInset.top + self.textContainerInset.bottom) / 2.0f;
}
return intrinsicContentSize;
}
@end
回答by DaNLtR
回答by Michael Link
I've found it's not entirely uncommon in situations where you may still need isScrollEnabled set to true to allow a reasonable UI interaction. A simple case for this is when you want to allow an auto expanding text view but still limit it's maximum height to something reasonable in a UITableView.
我发现在您可能仍然需要将 isScrollEnabled 设置为 true 以允许合理的 UI 交互的情况下,这并不少见。一个简单的情况是,当您希望允许自动扩展文本视图但仍将其最大高度限制为 UITableView 中的合理值时。
Here's a subclass of UITextView I've come up with that allows auto expansion with auto layout but that you could still constrain to a maximum height and which will manage whether the view is scrollable depending on the height. By default the view will expand indefinitely if you have your constraints setup that way.
这是我提出的 UITextView 的一个子类,它允许使用自动布局进行自动扩展,但您仍然可以限制为最大高度,并且它将根据高度管理视图是否可滚动。默认情况下,如果您以这种方式设置约束,则视图将无限扩展。
import UIKit
class FlexibleTextView: UITextView {
// limit the height of expansion per intrinsicContentSize
var maxHeight: CGFloat = 0.0
private let placeholderTextView: UITextView = {
let tv = UITextView()
tv.translatesAutoresizingMaskIntoConstraints = false
tv.backgroundColor = .clear
tv.isScrollEnabled = false
tv.textColor = .disabledTextColor
tv.isUserInteractionEnabled = false
return tv
}()
var placeholder: String? {
get {
return placeholderTextView.text
}
set {
placeholderTextView.text = newValue
}
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
isScrollEnabled = false
autoresizingMask = [.flexibleWidth, .flexibleHeight]
NotificationCenter.default.addObserver(self, selector: #selector(UITextInputDelegate.textDidChange(_:)), name: Notification.Name.UITextViewTextDidChange, object: self)
placeholderTextView.font = font
addSubview(placeholderTextView)
NSLayoutConstraint.activate([
placeholderTextView.leadingAnchor.constraint(equalTo: leadingAnchor),
placeholderTextView.trailingAnchor.constraint(equalTo: trailingAnchor),
placeholderTextView.topAnchor.constraint(equalTo: topAnchor),
placeholderTextView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var text: String! {
didSet {
invalidateIntrinsicContentSize()
placeholderTextView.isHidden = !text.isEmpty
}
}
override var font: UIFont? {
didSet {
placeholderTextView.font = font
invalidateIntrinsicContentSize()
}
}
override var contentInset: UIEdgeInsets {
didSet {
placeholderTextView.contentInset = contentInset
}
}
override var intrinsicContentSize: CGSize {
var size = super.intrinsicContentSize
if size.height == UIViewNoIntrinsicMetric {
// force layout
layoutManager.glyphRange(for: textContainer)
size.height = layoutManager.usedRect(for: textContainer).height + textContainerInset.top + textContainerInset.bottom
}
if maxHeight > 0.0 && size.height > maxHeight {
size.height = maxHeight
if !isScrollEnabled {
isScrollEnabled = true
}
} else if isScrollEnabled {
isScrollEnabled = false
}
return size
}
@objc private func textDidChange(_ note: Notification) {
// needed incase isScrollEnabled is set to true which stops automatically calling invalidateIntrinsicContentSize()
invalidateIntrinsicContentSize()
placeholderTextView.isHidden = !text.isEmpty
}
}
As a bonus there's support for including placeholder text similar to UILabel.
作为奖励,支持包含类似于 UILabel 的占位符文本。
回答by ma11hew28
You can also do it without subclassing UITextView
. Have a look at my answer to How do I size a UITextView to its content on iOS 7?
您也可以在没有子类化的情况下执行此操作UITextView
。看看我在 iOS 7 上如何将 UITextView 的大小调整到其内容的答案?
Use the value of this expression:
使用这个表达式的值:
[textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height
to update the constant
of the textView
's height UILayoutConstraint
.
更新constant
了的textView
的高度UILayoutConstraint
。
回答by DaveIngle
An important thing to note:
需要注意的重要事项:
Since UITextView is a subclass of UIScrollView, it is subject to the automaticallyAdjustsScrollViewInsets property of UIViewController.
由于 UITextView 是 UIScrollView 的子类,所以它受制于 UIViewController 的 automaticAdjustsScrollViewInsets 属性。
If you are setting up the layout and the TextView is the the first subview in a UIViewControllers hierarchy, it will have its contentInsets modified if automaticallyAdjustsScrollViewInsets is true sometimes causing unexpected behaviour in auto layout.
如果您正在设置布局并且 TextView 是 UIViewControllers 层次结构中的第一个子视图,则如果 automaticAdjustsScrollViewInsets 为 true,则它的 contentInsets 将被修改,有时会导致自动布局中出现意外行为。
So if you're having problems with auto layout and text views, try setting automaticallyAdjustsScrollViewInsets = false
on the view controller or moving the textView forward in the hierarchy.
因此,如果您在自动布局和文本视图方面遇到问题,请尝试automaticallyAdjustsScrollViewInsets = false
在视图控制器上设置或在层次结构中向前移动 textView。
回答by Honey
This more of a very important comment
这更重要的评论
Key to understanding why vitaminwater's answerworks are three things:
理解为什么维生素水的答案有效的关键是三件事:
- Know that UITextView is a subclass of UIScrollView class
- Understand how ScrollView works and how its contentSize is calculated. For more see this hereanswer and its various solutions and comments.
- Understand what contentSize is and how its calculated. See hereand here. It might also help that setting
contentOffset
is likelynothing but:
- 知道 UITextView 是 UIScrollView 类的子类
- 了解 ScrollView 的工作原理以及它的 contentSize 是如何计算的。有关更多信息,请参阅 此处的答案及其各种解决方案和评论。
- 了解 contentSize 是什么以及它是如何计算的。请参阅此处和此处。这也可以帮助该设置
contentOffset
是有可能只是:
func setContentOffset(offset: CGPoint)
{
CGRect bounds = self.bounds
bounds.origin = offset
self.bounds = bounds
}
For more see objc scrollviewand understanding scrollview
有关更多信息,请参阅objc scrollview和了解 scrollview
Combining the three together you'd easily understand that you need allow the the textView's intrinsic contentSizeto work along AutoLayout constraints of the textView to drive the logic. It's almost as if you're textView is functioning like a UILabel
将这三者结合在一起,您很容易理解您需要允许textView的内在 contentSize与 textView 的 AutoLayout 约束一起工作以驱动逻辑。就好像你的 textView 的功能就像 UILabel
To make that happen you need to disable scrolling which basically means the scrollView's size, the contentSize's size and in case of adding a containerView, then the containerView's size would all be the same. When they're the same you have NO scrolling. And you'd have 0
contentOffset
. Having 0
contentOffSet
means you've not scrolled down. Not even a 1 point down! As a result the textView will be all stretched out.
要实现这一点,您需要禁用滚动,这基本上意味着 scrollView 的大小、contentSize 的大小,并且在添加 containerView 的情况下,containerView 的大小将全部相同。当它们相同时,您没有滚动。你会有. 有意味着你没有向下滚动。连1分都没有!结果 textView 将全部被拉伸。0
contentOffset
0
contentOffSet
It's also worth nothing that 0
contentOffset
means that the scrollView's bounds and frame are identical.
If you scroll down 5 points then your contentOffset would be 5
, while your scrollView.bounds.origin.y - scrollView.frame.origin.y
would be equal to 5
也没有任何价值,这 意味着 scrollView 的边界和框架是相同的。如果您向下滚动 5 点,那么您的 contentOffset 将是,而您的将等于0
contentOffset
5
scrollView.bounds.origin.y - scrollView.frame.origin.y
5
回答by beebcon
Plug and Play Solution - Xcode 9
即插即用解决方案 - Xcode 9
Autolayoutjust like UILabel
, with the link detection, text selection, editingand scrollingof UITextView
.
Autolayout就像UILabel
,带有链接检测、文本选择、编辑和滚动的UITextView
。
Automatically handles
自动处理
- Safe area
- Content insets
- Line fragment padding
- Text container insets
- Constraints
- Stack views
- Attributed strings
- Whatever.
- 安全区
- 内容插入
- 行片段填充
- 文本容器插入
- 约束
- 堆栈视图
- 属性字符串
- 任何。
A lot of these answers got me 90% there, but none were fool-proof.
很多这些答案让我达到了 90%,但没有一个是万无一失的。
Drop in this UITextView
subclass and you're good.
加入这个UITextView
子类,你很好。
#pragma mark - Init
- (instancetype)initWithFrame:(CGRect)frame textContainer:(nullable NSTextContainer *)textContainer
{
self = [super initWithFrame:frame textContainer:textContainer];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit
{
// Try to use max width, like UILabel
[self setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
// Optional -- Enable / disable scroll & edit ability
self.editable = YES;
self.scrollEnabled = YES;
// Optional -- match padding of UILabel
self.textContainer.lineFragmentPadding = 0.0;
self.textContainerInset = UIEdgeInsetsZero;
// Optional -- for selecting text and links
self.selectable = YES;
self.dataDetectorTypes = UIDataDetectorTypeLink | UIDataDetectorTypePhoneNumber | UIDataDetectorTypeAddress;
}
#pragma mark - Layout
- (CGFloat)widthPadding
{
CGFloat extraWidth = self.textContainer.lineFragmentPadding * 2.0;
extraWidth += self.textContainerInset.left + self.textContainerInset.right;
if (@available(iOS 11.0, *)) {
extraWidth += self.adjustedContentInset.left + self.adjustedContentInset.right;
} else {
extraWidth += self.contentInset.left + self.contentInset.right;
}
return extraWidth;
}
- (CGFloat)heightPadding
{
CGFloat extraHeight = self.textContainerInset.top + self.textContainerInset.bottom;
if (@available(iOS 11.0, *)) {
extraHeight += self.adjustedContentInset.top + self.adjustedContentInset.bottom;
} else {
extraHeight += self.contentInset.top + self.contentInset.bottom;
}
return extraHeight;
}
- (void)layoutSubviews
{
[super layoutSubviews];
// Prevents flashing of frame change
if (CGSizeEqualToSize(self.bounds.size, self.intrinsicContentSize) == NO) {
[self invalidateIntrinsicContentSize];
}
// Fix offset error from insets & safe area
CGFloat textWidth = self.bounds.size.width - [self widthPadding];
CGFloat textHeight = self.bounds.size.height - [self heightPadding];
if (self.contentSize.width <= textWidth && self.contentSize.height <= textHeight) {
CGPoint offset = CGPointMake(-self.contentInset.left, -self.contentInset.top);
if (@available(iOS 11.0, *)) {
offset = CGPointMake(-self.adjustedContentInset.left, -self.adjustedContentInset.top);
}
if (CGPointEqualToPoint(self.contentOffset, offset) == NO) {
self.contentOffset = offset;
}
}
}
- (CGSize)intrinsicContentSize
{
if (self.attributedText.length == 0) {
return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);
}
CGRect rect = [self.attributedText boundingRectWithSize:CGSizeMake(self.bounds.size.width - [self widthPadding], CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
return CGSizeMake(ceil(rect.size.width + [self widthPadding]),
ceil(rect.size.height + [self heightPadding]));
}