web-dev-qa-db-fra.com

Redimensionner un UITextField lors de la frappe (en utilisant Autolayout)

J'ai un UITableViewCell qui a deux UITextFields (sans frontières). Les contraintes suivantes sont utilisées pour configurer la disposition horizontale.

@"|-10-[leftTextField(>=80)]-(>=10)-[rightTextField(>=40)]-10-|"

Rien d'extraordinaire et fonctionne comme prévu.

enter image description here

Comme vous pouvez le constater, la taille de textField supérieure est correcte. Le textField inférieur commençait avec un texte vide et, à cause du texte vide, il fait 80 points de large. Lorsque je tape du texte dans le champ d'édition textField, le texte défile vers la gauche, il ne change pas de largeur.

Je n'aime pas ça, la largeur de textField devrait s'ajuster pendant que l'utilisateur tape dans ce textField.

À mon avis, cela devrait fonctionner hors de la boîte. En implémentant une IBAction pour l'événement UIControlEventEditingChanged, je peux confirmer que le fait de taper change réellement la intrinsicContentSize de UITextField.

Mais bon, la largeur ne change pas tant que textField n’est plus le premier intervenant. Si je mets le curseur dans un autre champ de texte, la largeur du champ de texte modifié est définie. C'est un peu tard pour ce que je veux.

C'est ce que j'ai essayé sans succès: ces lignes commentées.

- (IBAction)textFieldDidChange:(UITextField *)textField {
    [UIView animateWithDuration:0.1 animations:^{
//        [self.contentView removeConstraints:horizontalConstraints];
//        [self.contentView addConstraints:horizontalConstraints];
//        [self.contentView layoutIfNeeded];

//        [self.contentView setNeedsLayout];

//        [self.contentView setNeedsUpdateConstraints];
    }];
    NSLog(@"%@", NSStringFromCGSize(textField.intrinsicContentSize));
}

Est-ce que quelqu'un sait ce que je manque? Que pourrais-je essayer de faire ce travail?

53
Matthias Bauch

C'est ce qui fonctionne pour moi:

- (IBAction) textFieldDidChange: (UITextField*) textField
{
    [UIView animateWithDuration:0.1 animations:^{
        [textField invalidateIntrinsicContentSize];
    }];
}

Ce qui est intéressant, c’est qu’il semble que cela DEVRAIT fonctionner immédiatement, étant donné ce texte du texte de la documentation :

De plus, si une propriété d'une vue change et que ce changement affecte la taille du contenu intrinsèque, la vue doit appeler invalidateIntrinsicContentSize de sorte que le système de présentation remarque le changer et peut re-mise en page. Dans l'implémentation de votre classe de vue, vous devez vous assurer que si la valeur de toute propriété sur laquelle le la taille intrinsèque dépend des changements, vous invoquez invalidateIntrinsicContentSize. Par exemple, un champ de texte appelle invalidateIntrinsicContentSize si la valeur de la chaîne change.

Ma meilleure hypothèse est que le champ de texte n'appelle invalidateIntrinsicContentSize qu'une fois l'édition terminée, pas pendant.

Edit: Un tas de "Cela ne marche pas pour moi". Je pense que la confusion ici est peut-être l'événement déclencheur qui est lié au gestionnaire textFieldDidChange:. L'événement doit être UIControlEventEditingChanged. Si vous utilisez IB, vérifiez que vous gérez le bon événement.

La variable UITextField ne peut pas non plus être limitée en taille. Vous pouvez le verrouiller avec des contraintes, mais toute contrainte de largeur ou un ensemble de contraintes de positionnement gauche + droite l'empêcheront de redimensionner à la taille de son contenu intrinsèque. 

54
TomSwift

Je ne sais pas pourquoi UITextField ne met à jour que intrinsicContentSize lorsqu'elle démissionne de son statut de premier répondant. Cela se produit à la fois pour iOS 7 et iOS 8.

En tant que solution temporaire, je remplace intrinsicContentSize et détermine la taille à l'aide de typingAttributes. Ceci représente également leftView et rightView

// This method is target-action for UIControlEventEditingChanged
func textFieldEditingChanged(textField: UITextField) {
  textField.invalidateIntrinsicContentSize()
}


override var intrinsicContentSize: CGSize {
    if isEditing {
      let string = (text ?? "") as NSString
      let size = string.size(attributes: typingAttributes)
      return CGSize(width: size.width + (rightView?.bounds.size.width ?? 0) + (leftView?.bounds.size.width ?? 0) + 2,
                    height: size.height)
    }

    return super.intrinsicContentSize
  }

Ici, je l’élargis pour tenir compte du curseur

17
onmyway133

Voici la réponse dans Swift. En prime, cet extrait ne réduit pas le champ de texte s'il existe un espace réservé.

// UITextField subclass

override func awakeFromNib() {
    super.awakeFromNib()
    self.addTarget(self, action: #selector(textFieldTextDidChange), forControlEvents: .EditingChanged)
}

func textFieldTextDidChange(textField: UITextField) {
    textField.invalidateIntrinsicContentSize()
}

override func intrinsicContentSize() -> CGSize {
    if self.editing {
        let textSize: CGSize = NSString(string: ((text ?? "" == "") ? self.placeholder : self.text) ?? "").sizeWithAttributes(self.typingAttributes)
        return CGSize(width: textSize.width + (self.leftView?.bounds.size.width ?? 0) + (self.rightView?.bounds.size.width ?? 0) + 2, height: textSize.height)
    } else {
        return super.intrinsicContentSize()
    }
}
4
WaltersGE1

Commençons par le premier : dans Swift 4, la propriété intrinsicContentSize de UITextField est une variable calculée et non une méthode. 

Vous trouverez ci-dessous une solution Swift4 avec un cas d'utilisation spécialisé. Un champ de texte justifié à droite peut redimensionner et s'étendre vers la gauche jusqu'à une certaine limite de pixels . Le champ de texte est affecté à la classe CurrencyTextField qui hérite de UITextField . CurrencyTextField est ensuite étendu pour fournir un comportement personnalisé permettant de renvoyer l'objet intrinsicContentSize , une variable calculée ..
L'événement " édition modifié " de l'instance CurrencyTextField est mappé sur la méthode repositionAndResize IBAction dans sa classe Host UIViewController. Cette méthode invalide simplement la taille de contenu intrinsèque actuelle de l'instance CurrencyTextField. L'appel de la méthode invalidateIntrinsicContentSize de CurrencyTextField (formulaire hérité UITextField) déclenche une tentative d'obtention de la taille de contenu intrinsèque de l'objet. 

La logique personnalisée de la méthode intrinsicContentSize getter de CurrencyTextField doit convertir la propriété text de CurrentTextField en NSString afin de déterminer sa taille, qu'elle renvoie en tant que intrinsicContentSize si la coordonnée x de CurrencyTextField est supérieure à la limite de pixel stipulée. 

class CurrencyTextField:UITextField {

}

extension CurrencyTextField {


     override open var intrinsicContentSize: CGSize {

        if isEditing {
            let string = (text ?? "") as NSString
            let stringSize:CGSize = string.size(withAttributes: 
             [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 
             19.0)])
             if self.frame.minX > 71.0 {
                return stringSize
             }
       }

       return super.intrinsicContentSize
    }


}

class TestController: UIViewController {

    @IBAction func repositionAndResize(_ sender: CurrencyTextField) {
        sender.invalidateIntrinsicContentSize()
    }


    override func viewDidLoad() {
        super.viewDidLoad()

    }

}
2
sigmonky

J'ai eu une exigence similaire avec un UITextView (dû augmenter dynamiquement sa hauteur et quelques autres choses).

Ce que j'ai fait était quelque chose de similaire à ceci:

Considérant que self.contentView est la superview de textField

- (IBAction)textFieldDidChange:(UITextField *)textField {
    //You can also use "textField.superview" instead of "self.contentView"
    [self.contentView setNeedsUpdateConstraints];

    //Since you wish to animate that expansion right away...
    [UIView animateWithDuration:0.1 animations:^{
        [self.contentView updateConstraintsIfNeeded];
    }];
    NSLog(@"%@", NSStringFromCGSize(textField.intrinsicContentSize));
}

De plus, compte tenu de mes exigences, je devais remplacer la méthode updateConstraints de ma UITextView 's superview.

Si vous optez pour cette solution (peut-être pour une approche plus précise), vous pouvez faire:

- (void)updateConstraints {
    [super updateConstraints];

    //Firstly, remove the width constraint from the textField.
    [self.myTextField removeConstraint:self.textFieldWidthConstraint];
    self.textFieldWidthConstraint = nil;

    CGSize contentSize = self.myTextField.intrinsicContentSize;
    self.textFieldWidthConstraint = [NSLayoutConstraint constraintWithItem:self.myTextField attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:contentSize.width];

    [self.myTextField addConstraint:self.textFieldWidthConstraint];
}

Comme mentionné, l'option de substitution a été utilisée par moi parce que je nécessitais une approche plus précise.

En outre, comme toujours, vous devrez vous assurer de vérifier tous les cas Edge (en termes de valeurs de dimensionnement) que vous pensez pouvoir rencontrer.

J'espère que cela t'aides!

À votre santé!

1
codeBearer

Pour une raison quelconque, appeler invalidateIntrinsicContentSize ne fonctionnait pas pour moi non plus, mais j'ai également rencontré des problèmes avec les autres solutions en raison des marges et des contrôles d'édition. 

Cette solution n'est pas toujours nécessaire. si invalidateIntrinsicContentSize fonctionne, il ne reste plus qu'à lui ajouter un appel lorsque le texte est modifié, comme dans les autres réponses. Si cela ne fonctionne pas, voici une solution (adaptée de ici ) que j'ai trouvée et qui fonctionne également avec un contrôle clair:

class AutoResizingUITextField: UITextField {
    var lastTextBeforeEditing: String?

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupTextChangeNotification()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupTextChangeNotification()
    }                

    func setupTextChangeNotification() {
        NotificationCenter.default.addObserver(
            forName: Notification.Name.UITextFieldTextDidChange,
            object: self,
            queue: OperationQueue.main) { (notification) in                   
                self.invalidateIntrinsicContentSize()
        }
        NotificationCenter.default.addObserver(
            forName: Notification.Name.UITextFieldTextDidBeginEditing,
            object: self,
            queue: OperationQueue.main) { (notification) in
                self.lastTextBeforeEditing = self.text
        }
    }                

    override var intrinsicContentSize: CGSize {
        var size = super.intrinsicContentSize

        if isEditing, let text = text, let lastTextBeforeEditing = lastTextBeforeEditing {
            let string = text as NSString
            let stringSize = string.size(attributes: typingAttributes)
            let origSize = (lastTextBeforeEditing as NSString).size(attributes: typingAttributes)
            size.width = size.width + (stringSize.width - origSize.width)
        }

        return size
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

Si invalidateIntrinsicContentSize fonctionne par lui-même , cela finira par compter deux fois le changement, ce qui doit être vérifié en premier.

0
Learn OpenGL ES

Pour moi, j'ai également ajouté une cible au champ de texte (UIControlEventEditingChanged)

- (void)textFieldChanged:(UITextField *)textField {
    [textField sizeToFit];
}

si vous avez une couleur de fond, mettez-la simplement à jour dans la méthode déléguée:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

    textField.backgroundColor = [UIColor grayColor];
    return YES;
}
0
robegamesios

Une autre solution consiste à définir des contraintes de début/fin sur le champ textField et à utiliser isActive pour les activer/désactiver.

Lorsque vous démarrez l’édition, activez-les. Si le texte est centré, l’effet sera le même (le texte semble redimensionner automatiquement au fur et à mesure que vous tapez). Lorsque les modifications s’arrêtent, désactivez les contraintes afin d’enrouler le cadre autour du texte.

0
bauerMusic

Vous pouvez ajouter ce code dans viewDidLoad pour résoudre le problème.

if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)])
{
    self.edgesForExtendedLayout = UIRectEdgeNone;
}
0
Eric Lee