web-dev-qa-db-fra.com

Modification du cadre d'un inputAccessoryView dans iOS 8

Lurker de longue date - première affiche!

Je rencontre un problème lorsque je recrée un bar avec une UITextView comme le fait WhatsApp.

J'utilise une sous-classe UIView personnalisée et l'instancie paresseusement sur:

- (UIView *)inputAccessoryView

et en retournant OUI le:

- (BOOL)canBecomeFirstResponder

Maintenant, je veux changer la taille de la inputAccessoryView lorsque la UITextView grandit. Sur iOS 7, je voudrais simplement changer la taille du cadre de cette vue - et non pas son origine -, puis appeler reloadInputViews et le système fonctionnerait: la vue serait déplacée vers le haut de sorte qu'elle soit entièrement visible au-dessus du clavier.

Sur iOS 8, cependant, cela ne fonctionne pas. La seule façon de le faire fonctionner est de modifier également l'origine du cadre en une valeur négative. Ce serait très bien, sauf que cela crée des bugs étranges: par exemple, la variable UIView revient au cadre "original" lors de la saisie de texte.

Y a-t-il quelque chose qui me manque? Je suis à peu près certain que WhatsApp utilise inputAccessoryView en raison de la façon dont ils déplacent le clavier en mode glisser - uniquement dans la dernière version de l'application.

S'il vous plaît laissez-moi savoir si vous pouvez m'aider! Ou s'il y a un test que vous voudriez que je lance!

Je vous remercie! :)

BTW, voici le code que j'utilise pour mettre à jour la hauteur de la vue UIView personnalisée appelée composeBar:

// ComposeBar frame size
CGRect frame = self.composeBar.frame;
frame.size.height += heightDifference;
frame.Origin.y -= heightDifference;
self.composeBar.frame = frame;
[self.composeBar.textView reloadInputViews]; // Tried with this
[self reloadInputViews];                     // and this

Edit: le code source complet est disponible @ https://github.com/manuelmenzella/SocketChat-iOS

26
Manuel Menzella

Je frappe ma tête contre le mur depuis un certain temps, alors que le comportement changeait d'iOS 7 à iOS 8. J'ai tout essayé, jusqu'à ce que la solution la plus évidente de toutes fonctionne pour moi:

inputAccessoryView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

duh!

31
JohnnyC

Pour résumer answer : de JohnnyC, définissez la variable autoresizingMask de votre inpitAccessoryView sur .flexibleHeight, calculez sa intrinsicContentSize et laissez la structure faire le reste.

Code complet, mis à jour pour Swift 3:

class InputAccessoryView: UIView, UITextViewDelegate {

    let textView = UITextView()

    override var intrinsicContentSize: CGSize {
        // Calculate intrinsicContentSize that will fit all the text
        let textSize = textView.sizeThatFits(CGSize(width: textView.bounds.width, height: CGFloat.greatestFiniteMagnitude))
        return CGSize(width: bounds.width, height: textSize.height)
    }

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

        // This is required to make the view grow vertically
        autoresizingMask = .flexibleHeight

        // Setup textView as needed
        addSubview(textView)
        textView.translatesAutoresizingMaskIntoConstraints = false
        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[textView]|", options: [], metrics: nil, views: ["textView": textView]))
        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[textView]|", options: [], metrics: nil, views: ["textView": textView]))

        textView.delegate = self

        // Disabling textView scrolling prevents some undesired effects,
        // like incorrect contentOffset when adding new line,
        // and makes the textView behave similar to Apple's Messages app
        textView.isScrollEnabled = false
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: UITextViewDelegate

    func textViewDidChange(_ textView: UITextView) {
        // Re-calculate intrinsicContentSize when text changes
        invalidateIntrinsicContentSize()
    }

}
16
maxkonovalov

Le problème est que dans iOS 8, une NSLayoutConstraint qui définit la hauteur de inputAccessoryView égale à sa hauteur de trame initiale est installée automatiquement. Pour résoudre le problème de mise en page, vous devez mettre à jour cette contrainte à la hauteur souhaitée, puis demander à votre inputAccessoryView de se présenter.

- (void)changeInputAccessoryView:(UIView *)inputAccessoryView toHeight:(CGFloat)height {
    for (NSLayoutConstraint *constraint in [inputAccessoryView constraints]) {
        if (constraint.firstAttribute == NSLayoutAttributeHeight) {
            constraint.constant = height;
            [inputAccessoryView layoutIfNeeded];
            break;
        }
    }
}
5
Rahul Jaswa

Voici une solution complète et autonome (merci @JohnnyC et @ JoãoNunes de m'avoir orienté dans la bonne direction, @stigi pour expliquant comment animer des modifications intrinsicContent): 

class InputAccessoryView: UIView {

    // InputAccessoryView is instantiated from nib, but it's not a requirement
    override func awakeFromNib() {
        super.awakeFromNib()        
        autoresizingMask = .FlexibleHeight
    }

    override func intrinsicContentSize() -> CGSize {
        let exactHeight = // calculate exact height of your view here
        return CGSize(width: UIViewNoIntrinsicMetric, height: exactHeight)
    }

    func somethingDidHappen() {
        // invalidate intrinsic content size, animate superview layout        
        UIView.animateWithDuration(0.2) {
            self.invalidateIntrinsicContentSize()
            self.superview?.setNeedsLayout()
            self.superview?.layoutIfNeeded()
        }
    }
}
5
Andrii Chernenko

Une solution simple et efficace à 100% consiste à énumérer toutes les contraintes et à définir une nouvelle valeur de hauteur. Voici un code C # (xamarin):

foreach (var constraint in inputAccessoryView.Constraints)
{
    if (constraint.FirstAttribute == NSLayoutAttribute.Height)
    {
        constraint.Constant = newHeight;
    }
}
3
Alexander Danilov

Oui, iOS8 ajoute une contrainte de hauteur privée à inputAccessoryView.

Compte tenu du fait que recréer toute l'entrée inputAccessoryView et remplacer l'ancien est une opération très coûteuse, vous pouvez simplement supprimer les contraintes avant de recharger les vues d'entrée

[inputAccessoryView removeConstraints:[inputAccessoryView constraints]];
[textView reloadInputViews];

Juste une autre solution de contournement

1
Vyacheslav Artemev

Malheureusement, iOS8 ajoute une contrainte de hauteur privée à inputAccessoryView, et cette contrainte est pas public.

Je recommande de recréer la vue accessoire lorsque son cadre doit changer et appelez reloadInputViews pour que le nouveau soit installé.

C’est ce que je fais et cela fonctionne comme prévu.

1
Gwendal Roué

Pour résoudre ce problème, j'ai utilisé inputAccessoryView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

Mais bien sûr, cela a entraîné la réduction de ma vue textuelle ..__ Donc, ajouter une contrainte à la barre d’outils et la mettre à jour lorsque nécessaire, ou ajouter la contrainte à la vue textuelle même et la mettre à jour a fonctionné pour moi.

0
Felipe

frist, obtenez inputAccessoryView et définissez nil

UIView *inputAccessoryView = yourTextView.inputAccessoryView;
yourTextView.inputAccessoryView = nil;
[yourTextView reloadInputViews];

puis définir le cadre et la mise en page

inputAccessoryView.frame = XXX
[inputAccessoryView setNeedsLayout];
[inputAccessoryView layoutIfNeeded];

dernier ensemble nouveau inputAccessoryView et recharger

yourTextView.inputAccessoryView = inputAccessoryView;
[yourTextView reloadInputViews];
0
maquannene