web-dev-qa-db-fra.com

Comment dimensionner un UITextView en fonction de son contenu sur iOS 7?

J'utilise la réponse acceptée ici depuis des années.

Sur iOS 7, contentSize.height devient frame.height-8, quel que soit le contenu du texte.

Quelle est la méthode de travail pour ajuster la hauteur sur iOS 7?

64
Henrik Erlandsson

Je suis favorable à ce changement de code minimal: ajoutez simplement ces deux lignes après addSubview et avant de saisir la height de la frame

...
[scrollView1 addSubview: myTextView];

    [myTextView sizeToFit]; //added
    [myTextView layoutIfNeeded]; //added

CGRect frame = myTextView.frame;
...

Ceci est testé rétro-compatible avec iOS 6.NOTEqu'il rétrécit envelopper la largeur. Si vous êtes simplement intéressé par la hauteur et que vous avez une largeur fixe, saisissez simplement la nouvelle hauteur mais définissez la largeur d'origine, et cela fonctionne comme avant sous iOS 6 et 7.

(Speculation: la taille s'adapte également à iOS 7, mais la mise en page est mise à jour ultérieurement ou dans un thread séparé, ce qui force la mise en page immédiatement afin que son cadre soit mis à jour à temps pour pouvoir utiliser sa hauteur lignes plus tard dans le même fil.)

REMARQUES:

1) Vous avez peut-être ou non implémenté le redimensionnement du conteneur externe de cette manière. Cela semble être un extrait courant, cependant, et je l’ai utilisé dans mes projets.

2) Puisque sizeToFit semble fonctionner comme prévu sur iOS 7, vous n'avez probablement pas besoin de la méthode prématurée addSubView. Que cela fonctionne toujours sur iOS 6 n'est pas testé par moi.

3) Spéculation: le coût moyen supplémentaire de layoutIfNeeded peut être coûteux. L'alternative telle que je la vois est de redimensionner le conteneur externe sur le rappel de présentation (déclenché ou non, selon que le système d'exploitation décide si la présentation est nécessaire ou non), le redimensionnement du conteneur externe provoquant une autre mise à jour de la présentation. Les deux mises à jour peuvent être combinées avec d'autres mises à jour de présentation pour être plus efficaces. Si vous avez une telle solution et que vous pouvez montrer qu'elle est plus efficace, ajoutez-la comme réponse et je ne manquerai pas de le mentionner ici.

55
Henrik Erlandsson

Depuis que j'utilise la mise en page automatique, j'utilise la valeur de [textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height pour mettre à jour la constant de la textView 's height UILayoutConstraint.

33
ma11hew28

J'utilise une version adaptée de la réponse de madmik qui élimine le facteur fudge:

- (CGFloat)measureHeightOfUITextView:(UITextView *)textView
{
    if ([textView respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)])
    {
        // This is the code for iOS 7. contentSize no longer returns the correct value, so
        // we have to calculate it.
        //
        // This is partly borrowed from HPGrowingTextView, but I've replaced the
        // magic fudge factors with the calculated values (having worked out where
        // they came from)

        CGRect frame = textView.bounds;

        // Take account of the padding added around the text.

        UIEdgeInsets textContainerInsets = textView.textContainerInset;
        UIEdgeInsets contentInsets = textView.contentInset;

        CGFloat leftRightPadding = textContainerInsets.left + textContainerInsets.right + textView.textContainer.lineFragmentPadding * 2 + contentInsets.left + contentInsets.right;
        CGFloat topBottomPadding = textContainerInsets.top + textContainerInsets.bottom + contentInsets.top + contentInsets.bottom;

        frame.size.width -= leftRightPadding;
        frame.size.height -= topBottomPadding;

        NSString *textToMeasure = textView.text;
        if ([textToMeasure hasSuffix:@"\n"])
        {
            textToMeasure = [NSString stringWithFormat:@"%@-", textView.text];
        }

        // NSString class method: boundingRectWithSize:options:attributes:context is
        // available only on ios7.0 sdk.

        NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
        [paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];

        NSDictionary *attributes = @{ NSFontAttributeName: textView.font, NSParagraphStyleAttributeName : paragraphStyle };

        CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
                                                  options:NSStringDrawingUsesLineFragmentOrigin
                                               attributes:attributes
                                                  context:nil];

        CGFloat measuredHeight = ceilf(CGRectGetHeight(size) + topBottomPadding);
        return measuredHeight;
    }
    else
    {
        return textView.contentSize.height;
    }
}
17
tarmes

Sur la base d’autres réponses, j’ai réussi à le faire fonctionner (dans Swift) . Ceci résout le problème du caractère de nouvelle ligne.

textView.sizeToFit()
textView.layoutIfNeeded()
let height = textView.sizeThatFits(CGSizeMake(textView.frame.size.width, CGFloat.max)).height
textView.contentSize.height = height

La mise en page automatique est nécessaire.

16
MKatleast3

Si vous utilisez la mise en page automatique, vous pouvez créer une sous-classe UITextView triviale qui redimensionne automatiquement la hauteur de la vue texte pour l'adapter au contenu:

@interface ContentHeightTextView : UITextView

@end

@interface ContentHeightTextView ()
@property (nonatomic, strong) NSLayoutConstraint *heightConstraint;
@end

@implementation ContentHeightTextView

- (void)layoutSubviews
{
    [super layoutSubviews];

    CGSize size = [self sizeThatFits:CGSizeMake(self.bounds.size.width, FLT_MAX)];

    if (!self.heightConstraint) {
        self.heightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0f constant:size.height];
        [self addConstraint:self.heightConstraint];
    }

    self.heightConstraint.constant = size.height;

    [super layoutSubviews];
}

@end

Bien entendu, la largeur et la position de la vue texte doivent être définies par des contraintes supplémentaires configurées ailleurs dans le programme.

Si vous créez cette vue de texte personnalisée dans IB, attribuez à cette dernière une contrainte de hauteur afin de satisfaire à Xcode. assurez-vous simplement que la contrainte de hauteur créée dans IB est simplement un espace réservé (c.-à-d., cochez la case "Supprimer au moment de la construction").

enter image description here

Une autre manière de mettre en œuvre la sous-classe UITextView est la suivante (cette mise en œuvre peut être qualifiée de meilleure pratique):

@interface ContentHeightTextView ()
@property (nonatomic, strong) NSLayoutConstraint *heightConstraint;
@end

@implementation ContentHeightTextView

- (void)layoutSubviews
{
    [super layoutSubviews];

    [self setNeedsUpdateConstraints];
}

- (void)updateConstraints
{
    CGSize size = [self sizeThatFits:CGSizeMake(self.bounds.size.width, FLT_MAX)];

    if (!self.heightConstraint) {
        self.heightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0f constant:size.height];
        [self addConstraint:self.heightConstraint];
    }

    self.heightConstraint.constant = size.height;
    [super updateConstraints];
}

@end
14
bilobatum

Si vous utilisez la disposition automatique, vous pouvez utiliser la sous-classe UITextView suivante qui ajoute une hauteur intrinsèque:

@implementation SelfSizingTextView

- (void)setText:(NSString *)text
{
    [super setText:text];
    [self invalidateIntrinsicContentSize];
}

- (void)setFont:(UIFont *)font
{
    [super setFont:font];
    [self invalidateIntrinsicContentSize];
}

- (CGSize)intrinsicContentSize
{
    CGFloat width = self.frame.size.width;
    CGSize size = [self sizeThatFits:CGSizeMake(width, MAXFLOAT)];
    return CGSizeMake(UIViewNoIntrinsicMetric, size.height);
}

@end
4
phatmann

cette méthode semble fonctionner.

// Code from Apple developer forum - @Steve Krulewitz, @Mark Marszal, @Eric Silverberg
- (CGFloat)measureHeight
{
if ([self respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)])
{
    CGRect frame = internalTextView.bounds;
    CGSize fudgeFactor;
    // The padding added around the text on iOS6 and iOS7 is different.
    fudgeFactor = CGSizeMake(10.0, 16.0);

    frame.size.height -= fudgeFactor.height;
    frame.size.width -= fudgeFactor.width;

    NSMutableAttributedString* textToMeasure;
    if(internalTextView.attributedText && internalTextView.attributedText.length > 0){
        textToMeasure = [[NSMutableAttributedString alloc] initWithAttributedString:internalTextView.attributedText];
    }
    else{
        textToMeasure = [[NSMutableAttributedString alloc] initWithString:internalTextView.text];
        [textToMeasure addAttribute:NSFontAttributeName value:internalTextView.font range:NSMakeRange(0, textToMeasure.length)];
    }

    if ([textToMeasure.string hasSuffix:@"\n"])
    {
        [textToMeasure appendAttributedString:[[NSAttributedString alloc] initWithString:@"-" attributes:@{NSFontAttributeName: internalTextView.font}]];
    }

    // NSAttributedString class method: boundingRectWithSize:options:context is
    // available only on ios7.0 sdk.
    CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
                                              options:NSStringDrawingUsesLineFragmentOrigin
                                              context:nil];

    return CGRectGetHeight(size) + fudgeFactor.height;
}
else
{
    return self.internalTextView.contentSize.height;
}
}
3
madmik3

Si vous utilisez iOS 7+, vous pouvez simplement activer la mise en page automatique, épingler chacun des côtés de la vue texte sur le bord de sa vue parent, et tout fonctionne correctement. Aucun code supplémentaire requis.

1
Swindler

Dans le storyboard, si vous utilisez des contraintes, assurez-vous que votre superview est limité dans l'onglet "règle" du volet de droite du xcode pour UITextView. Mon problème était que j'avais une contrainte de -80 pts sur le 'Trailing space to'. 

enter image description here

0
DanWebster

La réponse donnée par bilobatum a parfaitement fonctionné. Avec la mise en page automatique, c’est-à-dire la sous-classification textview.

Si vous souhaitez limiter la hauteur de la vue texte, ajoutez une autre contrainte (je l'ai ajoutée à l'aide d'un storyboard, c'est-à-dire hauteur <= 166 (hauteur selon vos besoins)).

Ensuite, dans la sous-classe, réduisez la priorité de contrainte de hauteur à 750 (self.heightConstraint.priority = 750) pour éviter les conflits entre la contrainte de hauteur ajoutée dans la sous-classe et la contrainte de hauteur ajoutée dans le storyboard.

0
Sunil Bhosale

Dans iOS 8, vous héritez d'un contenu décalé du parent, dont vous devez également vous débarrasser.

Un exemple de sous-classe

// Originally from https://github.com/Nikita2k/resizableTextView

#import "ResizableTextView.h"

@implementation ResizableTextView

- (void) updateConstraints {

    // calculate contentSize manually
    // ios7 doesn't calculate it before viewDidAppear and we'll get here before
    CGSize contentSize = [self sizeThatFits:CGSizeMake(self.frame.size.width, FLT_MAX)];

    // set the height constraint to change textView height
    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
        if (constraint.firstAttribute == NSLayoutAttributeHeight) {
            constraint.constant = contentSize.height;
            *stop = YES;
        }
    }];

    [super updateConstraints];
}

- (void)setContentOffset:(CGPoint)contentOffset
{
    // In iOS 8 we seem to be inheriting the content offset from the parent.
    // I'm not interested
}

@end
0
Johan Forssell

J'ai écrit une catégorie sur UITextView:

- (CGSize)intrinsicContentSize {
    return self.contentSize;
}

- (void)setContentSize:(CGSize)contentSize {
    [super setContentSize:contentSize];
    [self invalidateIntrinsicContentSize];
}

Lorsque UIKit définit sa contentSize, UITextView ajuste son intrinsic content size. Cela joue bien avec autolayout.

0
Mert Buran

Les gars utilisant autolayout et votre taille ne fonctionnant pas, veuillez vérifier votre contrainte de largeur une fois. Si vous avez manqué la contrainte de largeur, la hauteur sera précise.

Pas besoin d'utiliser une autre API. une seule ligne résoudrait tout le problème.

[_textView sizeToFit];

Ici, je ne m'occupais que de la hauteur, en maintenant la largeur fixe et j'avais manqué la contrainte de largeur de TextView dans le storyboard.

Et c'était pour montrer le contenu dynamique des services.

J'espère que cela pourrait aider ..

0
Swaroop S