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ąć.
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];
}
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.
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
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:
Ustaw priorytet odporności na kompresję treści w pionie na 1000.
Obniż priorytet wysokości ograniczenia, klikając "Edytuj" w ograniczeniach. Niech będzie mniej niż 1000.
In Attributes Inspector:
- Odznacz Opcję "Włączone Przewijanie"
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
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.
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
.
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.
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:
- wiedz, że UITextView jest podklasą klasy UIScrollView
- 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.
- 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.
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
.
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.
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]));
}
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
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.
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
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