La propriété masquée ne peut pas être modifiée dans un bloc d'animation

J'ai deux UILabels incorporés dans un UIStackView. L'label du haut rest visible en permanence, mais l'label du bas est activée et désactivée via la propriété hidden . Je voulais que cet effet soit animé, donc je l'ai coincé dans un bloc d'animation:

 private func toggleResultLabel(value:Double) { if value == 0 { UIView.animateWithDuration(0.25) { () -> Void in self.resultLabel.hidden = true } } else { UIView.animateWithDuration(0.25) { () -> Void in // Something weird is happening. I had to add 3 of the same statements to get // the hidden flag to be false self.resultLabel.hidden = false self.resultLabel.hidden = false self.resultLabel.hidden = false } } } 

Le problème est que la propriété cachée ne changera pas à less que je répète l'instruction encore et encore (3 fois dans ce cas). Je l'ai trouvé en pénétrant dans la fermeture de l'animation et en voyant que la propriété ne changerait pas pour son affectation. Maintenant, je remarque le même problème apparaissant apparemment de nouveau au hasard. La valeur par défaut de la deuxième label est true , si cela est pertinent.

Y at-il quelque chose qui me manque ici, ou est-ce un bug?

Mise à jour : Pour ce que ça vaut, je l'ai fait en ajoutant removeArrangedSubview() et addArrangedSubview() :

 if value == 0 { UIView.animateWithDuration(0.25) { () -> Void in self.resultLabel.hidden = true self.heroStackView.removeArrangedSubview(self.resultLabel) } } else { UIView.animateWithDuration(0.25) { () -> Void in self.heroStackView.addArrangedSubview(self.resultLabel) self.resultLabel.hidden = false } } 

Sur iOS 11 et les UIStackView antérieures, lorsque vous UIStackView une UIStackView arrangedSubview d'une UIStackView aide de l'API d'animation UIView à plusieurs resockets, les valeurs de propriété cachées «emstacknt» et nécessitent plusieurs fois la valeur masquée à false avant que la valeur ne change.

Au travail, nous avons décidé d'utiliser une extension UIView avec une méthode de contournement qui ne se cache qu'une seule fois pour une valeur donnée.

 extension UIView { // Workaround for the UIStackView bug where setting hidden to true with animation // mulptiple times requires setting hidden to false multiple times to show the view. public func workaround_nonRepeatingSetHidden(hidden: Bool) { if self.hidden != hidden { self.hidden = hidden } } } 

C'est certainement un bug dans UIKit, consultez l' exemple de projet qui le reproduit clairement.

entrez la description de l'image ici

Il semble qu'il y ait une corrélation entre le nombre de fois que le drapeau caché est défini sur le même état et le nombre de fois qu'il doit être défini sur un état différent avant qu'il ne soit réellement modifié. Dans mon cas, j'avais un drapeau caché déjà défini sur YES avant qu'il ne soit à nouveau défini sur YES dans le bloc d'animation et cela a causé le problème où je devais appeler hidden = NO deux fois dans mon autre bloc d'animation pour le rendre visible à nouveau. Si j'ai ajouté plus de lignes hidden = YES dans le premier bloc d'animation pour la même vue, j'ai dû avoir plus de lignes cachées = NO dans le deuxième bloc d'animation. Cela peut être un bogue dans l'observation KVO de UIStackView pour l'indicateur caché qui ne vérifie pas si la valeur est réellement modifiée ou non avant de changer un état interne qui conduit à ce problème.

Pour corriger temporairement le problème (jusqu'à ce qu'Apple le corrige), j'ai créé une catégorie pour UIView et une méthode setHidden swizzled: vers une version qui vérifie d'abord la valeur d'origine et ne définit la nouvelle valeur que si elle diffère de l'original. Cela semble fonctionner sans aucun effet néfaste.

 @implementation UIView (MethodSwizzling) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(setHidden:); SEL swizzledSelector = @selector(UIStackViewFix_setHidden:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } - (void)UIStackViewFix_setHidden:(BOOL)hidden { if (hidden != self.hidden) { [self UIStackViewFix_setHidden:hidden]; } } @end 

En considérant le bug UIStackView, je décide de vérifier la propriété cachée.

 if myView.hidden != hidden { myView.hidden = hidden } 

Ce n'est pas la solution la plus élégante mais ça marche pour moi.

Semble être un bug d'Apple avec UIStackView. Voir la suite …

UIStackView: basculer caché avec des animations est bloqué en mode caché http://www.openradar.me/22819594

Ma solution, bien que pas idéale, était de cacher l'UIStackView sans animation.

Selon la réponse de Raz0 , qui a découvert que seul le paramètre isHidden si nécessaire, résout le problème, voici une solution de contournement rapide qui a fait en sorte que cela fonctionne pour moi. J'évite la méthode swizzling car elle est insortingnsèquement dangereuse, en faveur d'une approche show/hide qui ne devrait pas gâcher avec les methods d'origine:

 extension UIView { func show() { guard isHidden else { return } isHidden = false } func hide() { guard !isHidden else { return } isHidden = true } } 

Utilisez-le comme ceci:

 view.show() view.hide() 

Ce qui a fonctionné pour moi est de définir la propriété cachée en dehors de l'animation et ensuite d'animer layoutIfNeeded (), comme vous le feriez avec des contraintes d'animation:

 label.isHidden = true UIView.animate(withDuration: 3) { self.view.layoutIfNeeded() } 

où label est une sous-vue organisée d'un UIStackView.