programing

자동 레이아웃을 사용하여 텍스트로 확장되는 UITextView

css3 2023. 6. 24. 09:29

자동 레이아웃을 사용하여 텍스트로 확장되는 UITextView

자동 레이아웃을 프로그래밍 방식으로 사용하여 완전히 배치된 보기가 있습니다.보기 중앙에 UITextView가 있고 그 위와 아래 항목이 있습니다.모든 것이 정상적으로 작동하지만, 텍스트가 추가됨에 따라 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)
])

Interface Builder에서 이 작업을 수행하려면 텍스트 보기를 선택하고 Attributes Inspector에서 Scrolling Enabled(스크롤링 사용)의 선택을 취소한 후 제약 조건을 수동으로 추가합니다.

보기 위보기가 에는 " " ": " " " " " " " " "/ " " " " " " " " " 을 하는 해 보세요.UIStackView모든 것을 정리하는 것.

자동 레이아웃으로 모든 작업을 수행하는 것을 선호하는 사용자를 위한 솔루션은 다음과 같습니다.

In Size InSize Inspector:

  1. 콘텐츠 압축 저항 우선 순위를 수직으로 1000으로 설정합니다.

  2. 구속조건에서 "편집"을 클릭하여 구속조건 높이의 우선순위를 낮춥니다.1000 미만으로 해주시면 됩니다.

enter image description here

특성 검사기:

  1. "스크롤링 사용" 선택 취소

UITextView는 본질적인 ContentSize를 제공하지 않으므로 하위 분류하여 제공해야 합니다.자동으로 크기를 늘리려면 layoutSubviews에서 본질적인 ContentSize를 비활성화합니다.기본 내용이 아닌 다른 내용을 사용하는 경우삽입(권장하지 않음)은 본질적인 내용 크기 계산을 조정해야 할 수도 있습니다.

@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

는 UITextView와 함께 됩니다.setBounds자동 레이아웃에 의해.그래서 저는 이렇게 했습니다.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];
}

스토리보드를 통해 할 수 있으며, "스크롤링 사용"을 비활성화하기만 하면 됩니다:)

StoryBoard

합리적인 UI 상호 작용을 허용하기 위해 여전히 isScrollEnabled를 true로 설정해야 하는 상황에서 완전히 드문 일은 아닙니다.간단한 경우에는 자동 확장 텍스트 보기를 허용하면서도 최대 높이를 UI 테이블 보기에서 적절한 수준으로 제한할 수 있습니다.

여기 제가 생각해 낸 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과 유사한 자리 표시자 텍스트를 포함하는 것이 지원됩니다.

의 답들이 된다는 을 보여줍니다.scrollEnabled이것이 최선의 해결책입니다.왜 효과가 있는지 설명하기 위해 이 답변을 씁니다.

UITextView는 을 합니다.intrinsicContentSize은 경에만재산인 scrollEnabled == NO게터 방법의 분해는 다음과 같습니다.

- (CGSize)intrinsicContentSize {
  if (self.scrollEnabled) {
    return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);
  } else {
    // Calculate and return intrinsic content size based on current width.
  }
}

즉, 텍스트 보기의 너비가 충분히 제한되었는지 확인한 다음 자동 레이아웃 컨텐츠 포옹/압축 저항 우선순위를 통해 또는 수동 레이아웃 중에 값을 직접 사용하여 고유한 컨텐츠 높이를 사용할 수 있습니다.

유감스럽게도 이 동작은 문서화되지 않았습니다.Apple은 높이 제한, 하위 분류 등의 추가적인 제약 없이도 우리 모두의 골칫거리를 쉽게 줄일 수 있었습니다.

하위 분류 없이도 수행할 수 있습니다.UITextViewiOS 7에서 UITextView를 콘텐츠에 맞게 크기를 조정하는 방법에 대한답변을 살펴보십시오.

다음 식의 값을 사용합니다.

[textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height

constant의 시대의textView의 키 의UILayoutConstraint.

이것은 매우 중요한 논평입니다.

비타민 워터의 대답이 효과가 있는 이유를 이해하는 열쇠는 세 가지입니다.

  1. UITextView가 UIScrollView 클래스의 하위 클래스임을 확인합니다.
  2. ScrollView의 작동 방식과 contentSize가 계산되는 방식을 이해합니다.자세한 내용은 여기에서 답변과 다양한 솔루션 및 의견을 참조하십시오.
  3. contentSize가 무엇이고 어떻게 계산되는지 이해합니다.여기와 여기를 보세요.또한 그 설정에 도움이 될 수도 있습니다.contentOffset다음과 같은 것일 가능성이 높습니다.

func setContentOffset(offset: CGPoint)
{
    CGRect bounds = self.bounds
    bounds.origin = offset
    self.bounds = bounds
}

자세한 내용은 개체 스크롤 보기 및 스크롤 보기 이해를 참조하십시오.


이 세 가지를 결합하면 textView의 고유 contentSize가 textView의 자동 레이아웃 제약 조건을 따라 작동하여 논리를 구동해야 한다는 것을 쉽게 이해할 수 있습니다.마치 텍스트 보기가 UI 레이블처럼 작동하는 것과 같습니다.

이렇게 하려면 스크롤을 비활성화해야 합니다. 이는 기본적으로 ScrollView의 크기, contentSize의 크기, 컨테이너View를 추가할 경우 컨테이너View의 크기가 모두 동일하다는 것을 의미합니다.동일한 경우 스크롤할 수 없습니다.그리고 당신은 contentOffset가지고 있는 contentOffSet아래로 스크롤하지 않았다는 뜻입니다.1점도 안 떨어졌습니다!결과적으로 텍스트 보기가 모두 확장됩니다.

그것은 또한 가치가 없습니다. contentOffsetscrollView의 경계와 프레임이 동일함을 의미합니다.5포인트 아래로 스크롤하면 컨텐츠 오프셋이 다음과 같습니다.5네가 있는 동안에scrollView.bounds.origin.y - scrollView.frame.origin.y와 같음5

자동으로 최대 높이까지 확대된 다음 스크롤할 수 있는 텍스트 보기가 필요했습니다.마이클 링크의 대답은 훌륭했지만, 저는 제가 조금 더 간단한 것을 생각해 낼 수 있는지 알고 싶었습니다.제가 생각해 낸 것은 다음과 같습니다.

Swift 5.3, Xcode 12

class AutoExpandingTextView: UITextView {

    private var heightConstraint: NSLayoutConstraint!

    var maxHeight: CGFloat = 100 {
        didSet {
            heightConstraint?.constant = maxHeight
        }
    }

    private var observer: NSObjectProtocol?

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        commonInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }

    private func commonInit() {
        heightConstraint = heightAnchor.constraint(equalToConstant: maxHeight)

        observer = NotificationCenter.default.addObserver(forName: UITextView.textDidChangeNotification, object: nil, queue: .main) { [weak self] _ in
            guard let self = self else { return }
            self.heightConstraint.isActive = self.contentSize.height > self.maxHeight
            self.isScrollEnabled = self.contentSize.height > self.maxHeight
            self.invalidateIntrinsicContentSize()
        }
    }
}


주의해야 할 중요한 사항:

UITextView는 UIScrollView의 하위 클래스이므로 자동 조정스크롤뷰의 적용을 받습니다.UIViewController의 속성을 삽입합니다.

레이아웃을 설정할 때 TextView가 UIViewControllers 계층의 첫 번째 하위 보기인 경우, 해당 내용이 포함됩니다.자동으로 조정되는 경우 수정된 삽입물 스크롤 뷰삽입값이 참인 경우가 있어 자동 레이아웃에서 예기치 않은 동작이 발생할 수 있습니다.

자동 레이아웃 및 텍스트 보기에 문제가 있는 경우 설정해 보십시오.automaticallyAdjustsScrollViewInsets = false보기 컨트롤러에서 또는 텍스트 이동계층에서 앞으로 보기를 선택합니다.

플러그 앤 플레이 솔루션 - Xcode 9

다음과 같이 자동 레이아웃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]));
}

비타민 워터의 답은 저에게 효과가 있습니다.

편집 중 텍스트 보기의 텍스트가 위아래로 튕겨져 있는 경우 설정 후[textView setScrollEnabled:NO];,세트Size Inspector > Scroll View > Content Insets > Never.

도움이 되길 바랍니다.

숨겨진 UI 레이블을 텍스트 보기 아래에 배치합니다.레이블 선 = 0. UI 텍스트 뷰의 제약 조건을 UI 레이블(중심 X, 중심 Y, 너비, 높이)과 동일하게 설정합니다.textView의 스크롤 동작을 그대로 두더라도 작동합니다.

그나저나, 저는 하위 클래스를 사용하고 본질적인 콘텐츠 크기를 재정의하는 확장 UITextView를 구축했습니다.UITextView에서 자체 구현에서 조사할 수 있는 버그를 발견했습니다.문제는 다음과 같습니다.

확장 텍스트 보기는 한 번에 한 글자씩 입력하는 경우 증가하는 텍스트에 맞게 축소됩니다.그러나 텍스트 묶음을 붙여넣으면 텍스트가 줄어들지 않고 텍스트가 위로 스크롤되고 맨 위에 있는 텍스트가 보이지 않게 됩니다.

해결책:하위 클래스에서 setBounds:을(를) 재정의합니다.알 수 없는 이유로, 붙여넣기로 인해 bounds.origin.y 값이 non-zee(내가 본 모든 경우 33)가 되었습니다.그래서 setBounds를 오버라이드했습니다: bounds.origin.y를 항상 0으로 설정하는 것입니다.문제를 해결했습니다.

빠른 해결책은 다음과 같습니다.

clipsToBounds 속성을 텍스트 보기의 false로 설정한 경우 이 문제가 발생할 수 있습니다.단순히 삭제하면 문제가 해결됩니다.

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

언급URL : https://stackoverflow.com/questions/16868117/uitextview-that-expands-to-text-using-auto-layout