it-swarm.com.de

UITextView, die mit automatischem Layout zu Text erweitert wird

Ich habe eine Ansicht, die vollständig mit dem automatischen Layout programmgesteuert angelegt ist. Ich habe eine UITextView in der Mitte der Ansicht mit Elementen darüber und darunter. Alles funktioniert gut, aber ich möchte UITextView erweitern können, wenn Text hinzugefügt wird. Dies sollte alles darunter drücken, wenn es sich ausdehnt.

Ich weiß, wie man das "Federn und Streben" macht, aber gibt es ein automatisches Layout dafür? Der einzige Weg, den ich mir vorstellen kann, ist, die Einschränkung jedes Mal zu entfernen und wieder hinzuzufügen, wenn sie wachsen muss.

146
tharris

Die Ansicht, die UITextView enthält, wird von AutoLayout mit setBounds zugewiesen. Also, das habe ich getan. Das Superview stellt anfangs alle anderen Einschränkungen so ein, wie sie sein sollten, und am Ende habe ich eine spezielle Einschränkung für die Höhe von UITextView festgelegt und in einer Instanzvariablen gespeichert.

_descriptionHeightConstraint = [NSLayoutConstraint constraintWithItem:_descriptionTextView
                                 attribute:NSLayoutAttributeHeight 
                                 relatedBy:NSLayoutRelationEqual 
                                    toItem:nil 
                                 attribute:NSLayoutAttributeNotAnAttribute 
                                multiplier:0.f 
                                 constant:100];

[self addConstraint:_descriptionHeightConstraint];

In der setBounds-Methode habe ich dann den Wert der Konstante geändert.

-(void) setBounds:(CGRect)bounds
{
    [super setBounds:bounds];

    _descriptionTextView.frame = bounds;
    CGSize descriptionSize = _descriptionTextView.contentSize;

    [_descriptionHeightConstraint setConstant:descriptionSize.height];

    [self layoutIfNeeded];
}
27
ancajic

Zusammenfassung: Deaktivieren Sie das Scrollen Ihrer Textansicht und beschränken Sie die Höhe nicht.

Um dies programmgesteuert durchzuführen, geben Sie den folgenden Code in viewDidLoad ein:

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)
])

Wählen Sie dazu im Interface Builder die Textansicht aus, deaktivieren Sie im Attribut-Inspector das Kontrollkästchen "Bildlauf aktivieren" und fügen Sie die Einschränkungen manuell hinzu.

Hinweis: Wenn Sie andere Ansichten über/unter Ihrer Textansicht haben, sollten Sie eine UIStackView verwenden, um sie alle anzuordnen.

518
vitaminwater

UITextView stellt kein intrinsicContentSize bereit, Sie müssen es also subclassieren und angeben. Um es automatisch wachsen zu lassen, machen Sie die intrinsicContentSize in layoutSubviews ungültig. Wenn Sie etwas anderes als das standardmäßige contentInset verwenden (was ich nicht empfehle), müssen Sie möglicherweise die intrinsicContentSize-Berechnung anpassen.

@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
39
dhallman

Hier ist eine Lösung für Leute, die es vorziehen, alles automatisch zu machen:

In Size Inspector:

  1. Legen Sie die Priorität der Inhaltskomprimierungsresistenz vertikal auf 1000 fest.

  2. Verringern Sie die Priorität der Beschränkungshöhe, indem Sie unter "Einschränkungen" auf "Bearbeiten" klicken. Mache es weniger als 1000.

 enter image description here

In Attributes Inspector:

  1. Deaktivieren Sie "Scrollen aktiviert".
30
Michael Revlis

Sie können dies über das Storyboard tun, deaktivieren Sie einfach "Scrolling Enabled" :)

 StoryBoard

25
DaNLtR

Ich habe festgestellt, dass dies in Situationen nicht völlig ungewöhnlich ist, in denen Sie möglicherweise noch isScrollEnabled auf true setzen müssen, um eine vernünftige Interaktion mit der Benutzeroberfläche zu ermöglichen. Ein einfacher Fall dafür ist, wenn Sie eine automatisch expandierende Textansicht zulassen möchten, die maximale Höhe jedoch in UITableView auf einen angemessenen Wert beschränken.

Hier ist eine Unterklasse von UITextView, die ich mir ausgedacht habe. Sie ermöglicht die automatische Erweiterung mit automatischem Layout. Sie können sich jedoch auf eine maximale Höhe beschränken und festlegen, ob die Ansicht abhängig von der Höhe scrollbar ist. Standardmäßig wird die Ansicht unbegrenzt erweitert, wenn Sie Ihre Einschränkungen auf diese Weise einrichten.

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
    }
}

Als Bonus gibt es Unterstützung für das Einfügen von Platzhaltertext ähnlich zu UILabel.

10
Michael Link

Sie können dies auch ohne Unterklassifizierung von UITextView tun. Werfen Sie einen Blick auf meine Antwort auf Wie kann ich eine Größe von UITextView unter iOS 7 anpassen?

Verwenden Sie den Wert dieses Ausdrucks:

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

um die constant der textView Höhe UILayoutConstraint zu aktualisieren.

7
ma11hew28

Eine wichtige Sache zu beachten: 

Da UITextView eine Unterklasse von UIScrollView ist, unterliegt es der automaticAdjustsScrollViewInsets-Eigenschaft von UIViewController. 

Wenn Sie das Layout einrichten und die TextView die erste Unteransicht in einer UIViewControllers-Hierarchie ist, werden deren contentInsets geändert, wenn automatischAdjustsScrollViewInsets wahr ist und manchmal ein unerwartetes Verhalten im automatischen Layout verursacht.

Wenn Sie also Probleme mit der automatischen Layout- und Textansicht haben, setzen Sie automaticallyAdjustsScrollViewInsets = false im Ansichtscontroller oder verschieben Sie die textView in der Hierarchie nach vorne.

3
DaveIngle

Dies ist mehr ein sehr wichtiger Kommentar 

Der Schlüssel zum Verständnis, warum die Antwort von Vitaminwater funktioniert, sind drei Dinge:

  1. Beachten Sie, dass UITextView eine Unterklasse der UIScrollView-Klasse ist
  2. Verstehen, wie ScrollView funktioniert und wie dessen Inhalt berechnet wird. Für mehr sehen Sie diese hier Antwort und ihre verschiedenen Lösungen und Kommentare. 
  3. Verstehen, was contentSize ist und wie es berechnet wird. hier und hier

Wenn Sie die drei Elemente zusammenfassen, werden Sie leicht verstehen, dass die intrinsic contentSize von textView den AutoLayout-Einschränkungen von textView entsprechen muss, um die Logik zu steuern. Es ist fast so, als ob textView als UILabel funktioniert 

Damit dies geschehen kann, müssen Sie das Scrollen deaktivieren. Dies bedeutet im Wesentlichen die Größe der ScrollView, die Größe von contentSize. Falls Sie eine ContainerView hinzufügen, ist die Größe der ContainerView alle gleich. Wenn sie gleich sind, haben Sie KEINEN Bildlauf. Und Sie hätten 0contentOffset. Wenn Sie 0contentOffSet haben, bedeutet dies, dass Sie noch nicht gescrollt haben. Nicht einmal einen Punkt nach unten! Als Ergebnis wird die Textansicht gestreckt. Es ist auch nichts wert, dass 0contentOffset bedeutet, dass die Grenzen und der Rahmen der ScrollView identisch sind. 

Wenn Sie 5 Punkte nach unten scrollen, wäre Ihr contentOffset 5, während Ihr scrollView.bounds.Origin.y - scrollView.frame.Origin.y gleich 5 wäre.

1
Honey

Plug & Play-Lösung - Xcode 9

Autolayout Genau wie UILabel, mit der Linkerkennung , Textauswahl , Bearbeitung und Scrollen von UITextView

Automatisch behandelt 

  • Sicherer Bereich
  • Inhaltseinsätze
  • Linienfragmentfüllung
  • Textcontainereinsätze
  • Einschränkungen
  • Ansichten stapeln
  • Zugeschriebene Zeichenfolgen
  • Was auch immer.

Viele dieser Antworten brachten mich zu 90% dort, aber keine war idiotensicher. 

Füge diese UITextView-Unterklasse hinzu und du bist gut.


#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]));
}
0
beebcon

die Antwort von vitaminwater funktioniert für mich. 

Wenn der Text Ihrer Textansicht während der Bearbeitung nach oben und unten springt, setzen Sie nach dem Festlegen von [textView setScrollEnabled:NO];Size Inspector > Scroll View > Content Insets > Never.

Ich hoffe es hilft.

0
Baron Ch'ng

Platzieren Sie versteckte UILabel unter Ihrer Textansicht. Beschriftungszeilen = 0. Setzen Sie die Einschränkungen für UITextView auf das UILabel (centerX, centerY, width, height). Funktioniert auch, wenn Sie das Scroll-Verhalten von TextView verlassen.

0
Yuriy

Hier ist eine schnelle Lösung:

Dieses Problem kann auftreten, wenn Sie die clipsToBounds-Eigenschaft auf false Ihrer Textansicht gesetzt haben. Wenn Sie es einfach löschen, wird das Problem behoben.

myTextView.clipsToBounds = false //delete this line
0
Eray Alparslan