我有一个以编程方式完全使用自动布局的视图。我在视图中间有一个UITextView,上面和下面都有项目。一切正常,但是我希望能够在添加文本时扩展UITextView。随着扩展,这应该将其下方的所有内容向下推。
我知道如何以“弹簧和支柱”的方式进行操作,但是是否有自动布局的方式来执行此操作?我能想到的唯一方法是每次需要增长时都删除并重新添加约束。
我有一个以编程方式完全使用自动布局的视图。我在视图中间有一个UITextView,上面和下面都有项目。一切正常,但是我希望能够在添加文本时扩展UITextView。随着扩展,这应该将其下方的所有内容向下推。
我知道如何以“弹簧和支柱”的方式进行操作,但是是否有自动布局的方式来执行此操作?我能想到的唯一方法是每次需要增长时都删除并重新添加约束。
Answers:
包含UITextView的视图setBounds
将由AutoLayout 分配其大小。所以,这就是我所做的。最初,superview设置了所有其他约束,最后,我为UITextView的高度设置了一个特殊约束,并将其保存在实例变量中。
_descriptionHeightConstraint = [NSLayoutConstraint constraintWithItem:_descriptionTextView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:0.f
constant:100];
[self addConstraint:_descriptionHeightConstraint];
setBounds
然后,在该方法中,我更改了常量的值。
-(void) setBounds:(CGRect)bounds
{
[super setBounds:bounds];
_descriptionTextView.frame = bounds;
CGSize descriptionSize = _descriptionTextView.contentSize;
[_descriptionHeightConstraint setConstant:descriptionSize.height];
[self layoutIfNeeded];
}
textViewDidChange
此功能应节省一些麻烦的子类化和编写代码……
textViewDidChange:
以编程方式将文本添加到UITextView时不会调用该方法。
摘要:禁用滚动文本视图,并且不限制其高度。
要以编程方式执行此操作,请将以下代码放入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)
])
为此,请在“界面生成器”中选择文本视图,取消选中“属性”检查器中的“启用滚动”,然后手动添加约束。
注意:如果在文本视图上方/下方有其他视图,请考虑使用a UIStackView
来排列所有视图。
intrinsicContentSize
像这样覆盖- (CGSize)intrinsicContentSize { CGSize size = [super intrinsicContentSize]; if (self.text.length == 0) { size.height = UIViewNoIntrinsicMetric; } return size; }
。告诉我它是否对您有用。我自己没有测试。
对于那些喜欢通过自动布局来完成全部工作的人来说,这是一个解决方案:
在尺寸检查器中:
将内容抗压缩优先级垂直设置为1000。
通过单击“约束”中的“编辑”来降低约束高度的优先级。使其小于1000。
在属性检查器中:
UITextView不提供internalContentSize,因此您需要对其进行子类化并提供一个。要使其自动增长,请在layoutSubviews中使nativeContentSize无效。如果您使用默认的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
setScrollEnabled:
将其始终设置为覆盖时,NO
对于不断增长的固有内容大小,您将获得一个无限循环。
[textView sizeToFit]
的textViewDidChange:
委托回调,以增大或缩小textview(并重新计算所有相关的约束)。
-(void)layoutSubviews
和包装所有的代码-(CGSize)intrinsicContentSize
与version >= 7.0f && version < 7.1f
+else return [super intrinsicContentSize];
我发现在仍然需要将isScrollEnabled设置为true以允许合理的UI交互的情况下,这种情况并不少见。一个简单的例子是,当您要允许自动展开的文本视图但仍将其最大高度限制在UITableView中的某个合理范围内时。
这是我提供的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
}
}
作为奖励,它支持包含类似于UILabel的占位符文本。
您也可以无需子类化UITextView
。看一下我如何在iOS 7上将UITextView调整为其内容的大小?
使用此表达式的值:
[textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height
更新constant
了的textView
的高度UILayoutConstraint
。
需要注意的重要事项:
由于UITextView是UIScrollView的子类,因此它受UIViewController的automaticAdjustsScrollViewInsets属性的约束。
如果要设置布局,并且TextView是UIViewControllers层次结构中的第一个子视图,则如果automaticAdjustsScrollViewInsets为true时,它将修改其contentInsets有时会导致自动布局中出现意外行为。
因此,如果您在使用自动布局和文本视图时遇到问题,请尝试automaticallyAdjustsScrollViewInsets = false
在视图控制器上进行设置或在层次结构中将textView向前移动。
这更是一个非常重要的评论
contentOffset
是:func setContentOffset(offset: CGPoint)
{
CGRect bounds = self.bounds
bounds.origin = offset
self.bounds = bounds
}
有关更多信息,请参见objc scrollview和了解scrollview
将这三种方法结合在一起,您将很容易理解,您需要允许textView 的固有contentSize沿着textView的AutoLayout约束来驱动逻辑。就像您在使用textView一样,就像UILabel一样
为此,您需要禁用滚动,这基本上意味着scrollView的大小,contentSize的大小,并且如果添加containerView,则containerView的大小将全部相同。当它们相同时,您不会滚动。而且你会有。拥有意味着您还没有向下滚动。甚至没有下降1点!结果,textView将全部拉伸。0
contentOffset
0
contentOffSet
它也一文不值,这 意味着scrollView的边界和框架是相同的。如果您向下滚动5点,则contentOffset将为,而您将等于0
contentOffset
5
scrollView.bounds.origin.y - scrollView.frame.origin.y
5
自动布局一样UILabel
,与链路检测,文本选择,编辑和滚动的UITextView
。
自动处理
这些答案很多都使我达到了90%,但没有一个是万无一失的。
放下这个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]));
}
顺便说一句,我使用子类构建了一个扩展的UITextView并覆盖了固有的内容大小。我在UITextView中发现了一个错误,您可能想在自己的实现中进行调查。这是问题所在:
如果您一次键入单个字母,则扩展的文本视图将向下扩展以适应不断增长的文本。但是,如果将一堆文本粘贴到其中,它不会变小,但文本会向上滚动,并且顶部的文本不可见。
解决方案:在子类中覆盖setBounds:。由于某些未知的原因,粘贴导致bounds.origin.y值变为非Zee(在我看到的每种情况下均为33)。因此,我将setBounds:覆盖为始终将bounds.origin.y设置为零。解决了问题。
这是一个快速的解决方案:
如果您将clipsToBounds属性设置为false的textview,则可能会出现此问题。如果只删除它,问题就消失了。
myTextView.clipsToBounds = false //delete this line
Obj C:
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (nonatomic) UITextView *textView;
@end
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
@synthesize textView;
- (void)viewDidLoad{
[super viewDidLoad];
[self.view setBackgroundColor:[UIColor grayColor]];
self.textView = [[UITextView alloc] initWithFrame:CGRectMake(30,10,250,20)];
self.textView.delegate = self;
[self.view addSubview:self.textView];
}
- (void)didReceiveMemoryWarning{
[super didReceiveMemoryWarning];
}
- (void)textViewDidChange:(UITextView *)txtView{
float height = txtView.contentSize.height;
[UITextView beginAnimations:nil context:nil];
[UITextView setAnimationDuration:0.5];
CGRect frame = txtView.frame;
frame.size.height = height + 10.0; //Give it some padding
txtView.frame = frame;
[UITextView commitAnimations];
}
@end