webentwicklung-frage-antwort-db.com.de

Ändern Sie die Größe von UITableViewCell, die UITextView enthält, nach der Eingabe

Ich habe einen UITableViewController, der eine benutzerdefinierte Zelle enthält. Jede Zelle wurde unter Verwendung einer Spitze erstellt und enthält eine einzige UITextView, die nicht scrollbar ist. Ich habe in jeder Zelle Einschränkungen hinzugefügt, damit die Zelle ihre Höhe an den Inhalt der UITextView anpasst. So sieht mein Controller zunächst so aus:

 initial state

Jetzt möchte ich, dass, wenn der Benutzer etwas in eine Zelle eingibt, sein Inhalt automatisch angepasst wird. Diese Frage wurde oft gestellt, siehe insbesondere hier oder die zweite Antwort hier . Ich habe also den folgenden Delegierten in meinen Code geschrieben: 

- (BOOL) textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text {
    [self.tableView beginUpdates];
    [self.tableView endUpdates];
    return YES;
}

Es führt jedoch zu dem folgenden merkwürdigen Verhalten: Alle Einschränkungen werden ignoriert und die Höhe aller Zellen wird auf den minimalen Wert reduziert. Siehe das Bild unten:

 wrong behavior

Wenn ich in der tableView nach unten und oben scrolle, um einen neuen Aufruf von cellForRowAtIndexPath zu erzwingen, ermittle ich die korrekten Höhen für die Zellen:

 correct behavior

Beachten Sie, dass not heightForRowAtIndexPath nicht implementiert hat, da AutoLayout davon ausgeht, dass es sich darum kümmert.

Könnte mir jemand sagen, was ich falsch gemacht habe, oder mir hier draußen helfen? Vielen Dank !

13
vib

Hier ist eine schnelle Lösung, die für mich gut funktioniert. Vorausgesetzt, Sie verwenden das automatische Layout, müssen Sie EstimRowHeight einen Wert zuweisen und dann UITableViewAutomaticDimension für die Zeilenhöhe zurückgeben. Führen Sie abschließend etwas Ähnliches in der Delegiertenansicht der Textansicht aus.

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.estimatedRowHeight = 44.0
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

// MARK: UITextViewDelegate
func textViewDidChange(textView: UITextView) {

    // Calculate if the text view will change height, then only force 
    // the table to update if it does.  Also disable animations to
    // prevent "jankiness".

    let startHeight = textView.frame.size.height
    let calcHeight = textView.sizeThatFits(textView.frame.size).height  //iOS 8+ only

    if startHeight != calcHeight {

        UIView.setAnimationsEnabled(false) // Disable animations
        self.tableView.beginUpdates()
        self.tableView.endUpdates()

        // Might need to insert additional stuff here if scrolls
        // table in an unexpected way.  This scrolls to the bottom
        // of the table. (Though you might need something more
        // complicated if editing in the middle.)

        let scrollTo = self.tableView.contentSize.height - self.tableView.frame.size.height
        self.tableView.setContentOffset(CGPointMake(0, scrollTo), animated: false)

        UIView.setAnimationsEnabled(true)  // Re-enable animations.
}
36
atlwx

Meine Lösung ist ähnlich wie @atlwx, aber etwas kürzer. Getestet mit statischer Tabelle. UIView.setAnimationsEnabled(false) ist erforderlich, um zu verhindern, dass der Inhalt der Zelle "springt", während die Tabelle die Zellenhöhe aktualisiert

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.estimatedRowHeight = 44.0
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

func textViewDidChange(_ textView: UITextView) {
    UIView.setAnimationsEnabled(false)
    textView.sizeToFit()
    self.tableView.beginUpdates()
    self.tableView.endUpdates()
    UIView.setAnimationsEnabled(true)
}
6
Yaiba

Das folgende Beispiel funktioniert für die dynamische Zeilenhöhe, wenn der Benutzer Text in die Zelle eingibt. Selbst wenn Sie das automatische Layout verwenden, müssen Sie die Methode heightForRowAtIndexPath noch implementieren. In diesem Beispiel müssen die Einschränkungen für die Arbeit auf textView gesetzt werden, sodass textView bei einer Erhöhung der Zellenhöhe ebenfalls an Höhe zunimmt. Dies kann durch Hinzufügen einer oberen Einschränkung und einer unteren Einschränkung von textView zur Zelleninhaltsansicht erreicht werden. Legen Sie jedoch keine Höhenbeschränkung für textView selbst fest. Aktivieren Sie außerdem das Scrollen für die textView, damit die Inhaltsgröße von textView aktualisiert wird, wenn der Benutzer Text eingibt. Dann verwenden wir diese Inhaltsgröße, um die neue Zeilenhöhe zu berechnen. Solange die Zeilenhöhe groß genug ist, um die textView vertikal zu dehnen, um mindestens der Inhaltsgröße zu entsprechen, wird die Textansicht nicht gescrollt, auch wenn das Scrollen aktiviert ist. Dies ist meiner Meinung nach erforderlich. 

In diesem Beispiel habe ich nur eine einzige Zeile und verwende nur eine einzige Variable, um die Zeilenhöhe zu verfolgen. Wenn wir jedoch mehrere Zeilen haben, benötigen wir für jede Zeile eine Variable, ansonsten haben alle Zeilen dieselbe Höhe. In diesem Fall kann ein Array von rowHeight verwendet werden, das dem tableView-Datenquellenarray entspricht.

@interface ViewController ()

@property (nonatomic, assign)CGFloat rowHeight;;
@property (weak, nonatomic) IBOutlet UITableView *tableView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.rowHeight = 60;
}

#pragma mark - UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 1;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell1"];
    return cell;
}

#pragma mark - UITableViewDelegate

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return self.rowHeight;
}


#pragma mark - UITextViewDelegate

- (void)textViewDidChange:(UITextView *)textView {
    [self.tableView beginUpdates];
    CGFloat paddingForTextView = 40; //Padding varies depending on your cell design
    self.rowHeight = textView.contentSize.height + paddingForTextView;
    [self.tableView endUpdates];
}

@end
4

Getestet unter iOS 12

Ich habe wirklich versucht viel Lösungen und schließlich eine gute gefunden hier

Dies funktioniert mit Animation und sieht wunderschön aus. Der Trick war das DispatchQueue.async Block. Ich habe auch TPKeyboardAvoidingTableView verwendet, um sicherzustellen, dass sich die Tastatur nicht überlappt.

func textViewDidChange(_ textView: UITextView) {
    // Animated height update
    DispatchQueue.main.async {
        self.tableView?.beginUpdates()
        self.tableView?.endUpdates()
    }        
}

[~ # ~] Update [~ # ~]

Ich habe seltsame Probleme beim Springen wegen TPKeyboardAvoidingTableView. Besonders wenn ich nach unten gescrollt habe und dann ein UITextView aktiv wurde. Also habe ich TPKeyboardAvoidingTableView durch native UITableView ersetzt und die Einfügungen selbst vorgenommen. In der Tabellenansicht wird nativ gescrollt.

3
fl034

Die Verwendung von Swift 2.2 (frühere Versionen würden wahrscheinlich ebenfalls funktionieren), wenn Sie für TableView die Verwendung von automatischen Bemaßungen festlegen (vorausgesetzt, Sie arbeiten mit einer Unterklasse UITableViewController wie folgt:

    self.tableView.rowHeight = UITableViewAutomaticDimension
    self.tableView.estimatedRowHeight = 50 // or something

Sie müssen lediglich den Delegaten in dieser Datei, UITextViewDelegate, implementieren und die unten stehende Funktion hinzufügen, und es sollte funktionieren. Denken Sie daran, Ihren Delegierten für textView auf self zu setzen (also vielleicht, nachdem Sie die Zelle aus der Warteschlange entfernt haben, cell.myTextView.delegate = self).

func textViewDidChange(textView: UITextView) {
    self.tableView.beginUpdates()
    textView.frame = CGRectMake(textView.frame.minX, textView.frame.minY, textView.frame.width, textView.contentSize.height + 40)
    self.tableView.endUpdates()
}

Vielen Dank an "Jose Tomy Joseph" für die Anregung dieser Antwort. 

3

Ich habe einen ähnlichen Ansatz mit einem UITextView implementiert, aber dazu musste ich heightForRowAtIndexPath implementieren.

#pragma mark - SizingCell

- (USNTextViewTableViewCell *)sizingCell
{
    if (!_sizingCell)
    {
        _sizingCell = [[USNTextViewTableViewCell alloc] initWithFrame:CGRectMake(0.0f,
                                                                                 0.0f,
                                                                                 self.tableView.frame.size.width,
                                                                                 0.0f)];
    }

    return _sizingCell;
}

#pragma mark - UITableViewDelegate

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    self.sizingCell.textView.text = self.profileUpdate.bio;

    [self.sizingCell setNeedsUpdateConstraints];
    [self.sizingCell updateConstraintsIfNeeded];

    [self.sizingCell setNeedsLayout];
    [self.sizingCell layoutIfNeeded];

    CGSize cellSize = [self.sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    return cellSize.height;
}

sizingCell ist eine Instanz der Zelle, die nur für Größenberechnungen verwendet wird.

Es ist wichtig zu beachten, dass Sie den oberen und unteren Rand des UITextView an den oberen und unteren Rand des UITableViewCells contentView anhängen müssen, damit sich die Höhe des UITextView auch in der Höhe ändert.

Für das Constraint-Layout verwende ich ein PureLayout ( https://github.com/smileyborg/PureLayout ), sodass der folgende Constraint-Layout-Code für Sie ungewöhnlich sein kann:

#pragma mark - Init

- (id)initWithStyle:(UITableViewCellStyle)style
    reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style
                reuseIdentifier:reuseIdentifier];

    if (self)
    {
        [self.contentView addSubview:self.textView];
    }

    return self;
}

#pragma mark - AutoLayout

- (void)updateConstraints
{
    [super updateConstraints];

    /*-------------*/

    [self.textView autoPinEdgeToSuperviewEdge:ALEdgeLeft
                                    withInset:10.0f];

    [self.textView autoPinEdgeToSuperviewEdge:ALEdgeTop
                                    withInset:5.0f];

    [self.textView autoPinEdgeToSuperviewEdge:ALEdgeBottom
                                    withInset:5.0f];

    [self.textView autoSetDimension:ALDimensionWidth
                             toSize:200.0f];
}
1
williamb

Inspiriert durch die beiden vorherigen Antworten fand ich einen Weg, mein Problem zu lösen. Ich denke, dass die Tatsache, dass ich eine UITextView hatte, einige Probleme mit autoLayout verursacht hat. Ich habe meinem ursprünglichen Code die folgenden zwei Funktionen hinzugefügt.

- (CGFloat)textViewHeightForAttributedText: (NSAttributedString*)text andWidth: (CGFloat)width {
    UITextView *calculationView = [[UITextView alloc] init];
    [calculationView setAttributedText:text];
    CGSize size = [calculationView sizeThatFits:CGSizeMake(width, FLT_MAX)];
    return size.height;
}


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UIFont *font = [UIFont systemFontOfSize:14.0];
    NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
    NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:self.sampleStrings[indexPath.row] attributes:attrsDictionary];
    return [self textViewHeightForAttributedText:attrString andWidth:CGRectGetWidth(self.tableView.bounds)-31]+20;
}

wobei in der letzten Zeile 31 die Summe meiner Einschränkungen auf der linken und rechten Seite der Zelle ist und 20 nur ein beliebiger Spielraum ist.

Ich fand diese Lösung beim Lesen dieser dieser sehr interessanten Antwort .

0
vib

Wenn Sie die Zellenhöhe der Tableview-Zellen sofort auf sanfte Weise aktualisieren möchten, ohne die Tastatur zu schließen, müssen Sie das folgende Snippet ausführen, das im textViewDidChange-Ereignis aufgerufen wird, nachdem Sie die Größe des textView oder anderer Inhalte der Zelle festgelegt haben:

[tableView beginUpdates];
[tableView endUpdates];

Dieser Wille kann jedoch nicht ausreichen. Sie sollten auch sicherstellen, dass das tableView genug Elastizität aufweist, um das gleiche contentOffset beizubehalten. Diese Elastizität erhalten Sie, wenn Sie tableView contentInset unten setzen. Ich schlage vor, dass dieser Elastizitätswert mindestens der maximalen Entfernung entspricht, die Sie vom unteren Ende der letzten Zelle bis zum unteren Rand des tableView benötigen. Beispielsweise kann es sich um die Höhe der Tastatur handeln. 

self.tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0);

Weitere Informationen und einige nützliche Zusatzfunktionen zu diesem Thema finden Sie unter folgendem Link: Ändern Sie die Größe und verschieben Sie UITableViewCell reibungslos, ohne die Tastatur zu schließen

0
Mig70

Die von fast jedem vorgeschlagene Lösung ist der Weg zu gehen, ich werde nur eine kleine Verbesserung hinzufügen. Zur Wiederholung:

  1. Einfach die geschätzte Höhe einstellen, ich mache es via Storyboard:  Cell height in storyboard  TableView row height estimates

  2. Stellen Sie sicher, dass Sie die Einschränkungen für die UITextView korrekt in der Zelle festgelegt haben.

  3. In der func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

Ich rufe einfach an: cell.myTextView.sizeToFit()

0
MNassar