UITextView rozszerzający się do tekstu przy użyciu układu automatycznego

Mam widok, który jest rozplanowany całkowicie za pomocą auto layout programowo. Mam UITextView w środku widoku z elementami powyżej i poniżej. Wszystko działa dobrze, ale chcę być w stanie rozwinąć UITextView jak tekst jest dodawany. Powinno to popchnąć wszystko pod nim w dół, gdy się rozszerza.

Wiem, jak to zrobić w sposób "sprężyny i rozpórki" , ale czy jest na to sposób auto layout? Jedynym sposobem, o którym mogę myśleć, jest usunięcie i ponowne dodanie ograniczenia za każdym razem musi rosnąć.

Author: tharris, 2013-06-01

15 answers

Widok zawierający UITextView zostanie przypisany jego rozmiar setBounds przez AutoLayout. Więc tak zrobiłem. Superview jest wstępnie skonfigurowany wszystkie inne ograniczenia tak, jak powinny być, a na koniec umieściłem jedno specjalne ograniczenie dla wysokości UITextView i zapisałem je w zmiennej instancji.

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

[self addConstraint:_descriptionHeightConstraint];

W metodzie setBounds zmieniłem wartość stałej.

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

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

    [_descriptionHeightConstraint setConstant:descriptionSize.height];

    [self layoutIfNeeded];
}
 25
Author: ancajic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-08 04:48:48

Podsumowanie: wyłącz przewijanie widoku tekstu i nie ograniczaj jego wysokości.

Aby to zrobić programowo, umieść następujący kod w 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)
])

Aby to zrobić w Kreatorze interfejsów, wybierz Widok tekstowy, odznacz opcję przewijanie włączone w Inspektorze atrybutów i dodaj ograniczenia ręcznie.

Uwaga: Jeśli masz inne widoki powyżej / poniżej widoku tekstowego, rozważ użycie UIStackView, aby uporządkować je wszystkie.

 497
Author: vitaminwater,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-06 14:39:01

UITextView nie dostarcza indrincontentsize, więc musisz go podklasować i dostarczyć. Aby rosła automatycznie, Unieważnij intrinsicContentSize w layoutSubviews. Jeśli używasz czegoś innego niż domyślny zestaw contentInset (którego nie polecam), może być konieczne dostosowanie obliczenia intrinsicContentSize.

@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
 37
Author: dhallman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-25 15:01:15

Oto rozwiązanie dla osób, które wolą robić to wszystko za pomocą auto layoutu:

In Size Inspector:

  1. Ustaw priorytet odporności na kompresję treści w pionie na 1000.

  2. Obniż priorytet wysokości ograniczenia, klikając "Edytuj" w ograniczeniach. Niech będzie mniej niż 1000.

Tutaj wpisz opis obrazka

In Attributes Inspector:

  1. Odznacz Opcję "Włączone Przewijanie"
 22
Author: Michael Revlis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-13 03:33:57

Możesz to zrobić poprzez storyboard, po prostu wyłącz "Włączanie przewijania":)

StoryBoard

 18
Author: DaNLtR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-13 08:16:15

Odkryłem, że nie jest to zupełnie rzadkie w sytuacjach, w których nadal możesz potrzebować isScrollEnabled ustawionego na true, aby umożliwić rozsądną interakcję z interfejsem użytkownika. Prostym przypadkiem jest to, gdy chcesz zezwolić na automatyczne Rozszerzanie widoku tekstu, ale nadal ograniczać jego maksymalną wysokość do czegoś rozsądnego w widoku UITableView.

Oto podklasa UITextView, którą wymyśliłem, która pozwala na automatyczną rozbudowę z automatycznym układem, ale nadal możesz ograniczyć do maksymalnej wysokości i która będzie zarządzać, czy widok można przewijać w zależności od wysokości. Domyślnie widok będzie rozszerzany w nieskończoność, jeśli masz ustawione ograniczenia w ten sposób.

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

Jako bonus istnieje wsparcie dla włączenia tekstu zastępczego podobnego do UILabel.

 10
Author: Michael Link,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-20 18:42:33

Można to również zrobić bez podklasowania UITextView. Spójrz na moja odpowiedź na pytanie jak dopasować UITextView do jego zawartości na iOS 7?

Użyj wartości tego wyrażenia:

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

Aby zaktualizować constant z wysokości textView UILayoutConstraint.

 7
Author: ma11hew28,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:48

Ważna rzecz do odnotowania:

Ponieważ UITextView jest podklasą UIScrollView, podlega właściwości automaticallyAdjustsScrollViewInsets UIViewController.

Jeśli konfigurujesz układ, a TextView jest pierwszym subviewem w hierarchii UIViewControllers, to jego zbiory zawartości zostaną zmodyfikowane, jeśli automatyczneadjustsscrollviewinsets są prawdziwe, czasami powodując nieoczekiwane zachowanie w układzie automatycznym.

Więc jeśli masz problemy z auto widoki układu i tekstu, spróbuj ustawić automaticallyAdjustsScrollViewInsets = false na kontrolerze widoku lub przesunąć widok tekstowy do przodu w hierarchii.

 3
Author: DaveIngle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-10 02:29:10

to bardziej bardzo ważny komentarz

Klucz do zrozumienia dlaczego odpowiedź vitaminwater prace to trzy rzeczy:

  1. wiedz, że UITextView jest podklasą klasy UIScrollView
  2. Zrozum, jak działa ScrollView i jak obliczana jest jego wielkość zawartości. Po więcej Zobacz to tutaj odpowiedź i jej różne rozwiązania i komentarze.
  3. zrozumieć, co to jest contentSize i jak jest obliczany. Zobacz tutaj i tutaj .

Łącząc te trzy razem łatwo zrozumiesz, że musisz zezwolić na wewnętrzną zawartość widoku textView , aby działać zgodnie z ograniczeniami AutoLayout widoku textView, aby napędzać logikę. To prawie tak, jakby textView działał jak UILabel

Aby tak się stało, musisz wyłączyć przewijanie, co w zasadzie oznacza rozmiar przewijania, rozmiar contentSize i w przypadku dodania containerView, wtedy Rozmiar containerView będzie bądź taki sam. Kiedy są takie same, nie masz przewijania. A Ty byś 0 contentOffset. Mając 0 contentOffSet oznacza, że nie przewijałeś w dół. Ani jednego punktu w dół! W rezultacie widok tekstu zostanie rozciągnięty.

 1
Author: Honey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-15 19:07:06

Odpowiedź Vitaminwater działa na mnie.

Jeśli tekst widoku textview jest odbijany w górę i w dół podczas edycji, po ustawieniu [textView setScrollEnabled:NO];, Ustaw Size Inspector > Scroll View > Content Insets > Never.

Mam nadzieję, że to pomoże.
 0
Author: Baron Ch'ng,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-30 15:40:15

Umieść Ukryty UILabel pod textview. Label lines = 0. Ustaw ograniczenia UITextView na równe UILabel (centerX, centerY, width, height). Działa nawet po pozostawieniu zachowania przewijania w textView.

 0
Author: Yuriy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-24 14:24:41

Rozwiązanie Plug and Play-Xcode 9

Autolayout podobnie jak UILabel, z detekcją łącza , wybór tekstu, edycja i przewijanie z UITextView.

Automatycznie obsługuje

  • bezpieczny obszar
  • wstawki treści
  • wyściełanie fragmentu linii
  • Wstawki kontenera tekstowego
  • Stack views
  • przypisane ciągi
  • Nieważne.

Wiele z tych odpowiedzi dostały mnie tam w 90%, ale żadna nie była niezawodna.

Wpadnij do tej podklasy i będzie dobrze.
#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
Author: beebcon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-10 16:26:56

Oto szybkie rozwiązanie:

Ten problem może wystąpić, jeśli właściwość clipstobounds została ustawiona na false w widoku tekstowym. Jeśli po prostu go usuniesz, problem zniknie.

myTextView.clipsToBounds = false //delete this line
 0
Author: Eray Alparslan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-16 13:46:03

BTW, zbudowałem rozszerzający UITextView używając podklasy i nadpisując Wewnętrzny rozmiar zawartości. Odkryłem błąd w UITextView, który możesz chcieć zbadać we własnej implementacji. Oto problem:

Widok rozwijającego się tekstu będzie rosnąć, aby pomieścić rosnący tekst, jeśli wpiszesz pojedyncze litery na raz. Ale jeśli wkleisz do niego kilka tekstu, nie będzie rosnąć, ale tekst będzie przewijać się w górę, a tekst na górze był poza zasięgiem wzroku.

The rozwiązanie: Override setBounds: w podklasie. Z jakiegoś nieznanego powodu, wklejanie spowodowało granice.pochodzenie.wartość y, aby nie była zee (33 w każdym przypadku, który widziałem). Więc przekroczyłem setBounds: aby zawsze ustawić granice.pochodzenie.y do zera. Naprawiono problem.

 -1
Author: Alan McKean,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-05 00:03:42
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
 -3
Author: A.G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-22 05:27:23