J'ai une configuration assez simple dans mon scénarimage principal:
J'ai un code qui traitera les vues actives changeantes de la vue comme suit:
import Foundation
import UIKit
class ViewController : UIViewController {
@IBOutlet weak var stackView: UIStackView!
@IBOutlet weak var segmentController: UISegmentedControl!
@IBAction func SegmentClicked(_ sender: AnyObject) {
updateView(segment: sender.titleForSegment(at: sender.selectedSegmentIndex)!)
}
override func viewDidLoad() {
updateView(segment: "First")
}
func updateView(segment: String) {
UIView.animate(withDuration: 1) {
if(segment == "First") {
self.stackView.arrangedSubviews[1].isHidden = false
self.stackView.arrangedSubviews[2].isHidden = true
} else {
self.stackView.arrangedSubviews[1].isHidden = true
self.stackView.arrangedSubviews[2].isHidden = false
}
print("Updating views")
print("View 1 is \(self.stackView.arrangedSubviews[1].isHidden ? "hidden" : "visible")")
print("View 2 is \(self.stackView.arrangedSubviews[2].isHidden ? "hidden" : "visible")")
}
}
}
Comme vous pouvez le constater, lorsque l'onglet "Premier" est sélectionné, la sous-vue de l'index 1 doit s'afficher, tandis que 2 est masquée et, si rien d'autre n'est sélectionné, la sous-vue de l'index 2 doit s'afficher, tandis que 1 est masqué.
Cela semble fonctionner au début, si je change lentement de vue, mais si je vais un peu plus vite, la vue de l'index 1 semble rester masquée en permanence après quelques clics, ce qui a pour résultat que la vue de l'index 0 couvre la totalité de l'écran. J'ai placé une animation montrant le problème et une capture d'écran du storyboard ci-dessous. La sortie montre que lorsque le problème survient, les deux vues restent masquées lorsque vous cliquez sur le premier segment.
Quelqu'un peut-il me dire pourquoi cela se produit? Est-ce un bug, ou est-ce que je ne fais pas quelque chose que je devrais être?
Merci d'avance!
Mise à jour: Il semble que je puisse reproduire le problème de manière fiable en allant dans le premier> deuxième> troisième> deuxième> premier segment dans cet ordre.
En fin de compte, après avoir essayé toutes les suggestions ici, je ne pouvais toujours pas comprendre pourquoi il se comportait de la sorte, alors j'ai contacté Apple qui m'a demandé de déposer un rapport de bogue. J'ai toutefois trouvé un moyen de contourner le problème en masquant d'abord les deux vues, ce qui a résolu mon problème:
func updateView(segment: String) {
UIView.animate(withDuration: 1) {
self.stackView.arrangedSubviews[1].isHidden = false
self.stackView.arrangedSubviews[2].isHidden = false
if(segment == "First") {
self.stackView.arrangedSubviews[2].isHidden = true
} else {
self.stackView.arrangedSubviews[1].isHidden = true
}
}
}
Le bogue est que cacher et montrer des vues dans une vue de pile est cumulatif. Bizarre bug Apple. Si vous masquez une vue dans une vue de pile deux fois, vous devez l'afficher deux fois pour la récupérer. Si vous le montrez trois fois, vous devez le cacher trois fois pour le cacher (en supposant qu'il était caché pour commencer).
Ceci est indépendant de l'utilisation de l'animation.
Donc, si vous faites quelque chose comme ceci dans votre code, masquant seulement une vue si elle est visible, vous éviterez ce problème:
if myView.isHidden == false {
myView.isHidden = true
}
En vous appuyant sur la réponse de Dave Batton à Nice, vous pouvez également ajouter une extension UIView pour rendre le site d’appel un peu plus propre, IMO.
extension UIView {
var isHiddenInStackView: Bool {
get {
return isHidden
}
set {
if isHidden != newValue {
isHidden = newValue
}
}
}
}
Ensuite, vous pouvez appeler stackView.subviews[someIndex].isHiddenInStackView = false
, ce qui est utile si vous avez plusieurs vues à gérer dans votre vue de pile par rapport à un tas d'instructions if.
D'après ce que je peux voir, ce comportement étrange est causé par la durée de l'animation. Comme vous pouvez le constater, l'animation prend une seconde, mais si vous commencez à basculer plus rapidement segmentControl, je dirais que c'est la cause de ce problème.
Ce que vous devez faire est de désactiver l’interactivité de l’utilisateur lorsque la méthode est appelée, puis de la réactiver une fois l’animation terminée.
Ça devrait ressembler a quelque chose comme ca:
func updateView(segment: String) {
segmentControl.userInteractionEnabled = false
UIView.animateWithDuration(1.0, animations: {
if(segment == "First") {
self.stackView.arrangedSubviews[1].isHidden = false
self.stackView.arrangedSubviews[2].isHidden = true
} else {
self.stackView.arrangedSubviews[1].isHidden = true
self.stackView.arrangedSubviews[2].isHidden = false
}
print("Updating views")
print("View 1 is \(self.stackView.arrangedSubviews[1].isHidden ? "hidden" : "visible")")
print("View 2 is \(self.stackView.arrangedSubviews[2].isHidden ? "hidden" : "visible")")
}, completion: {(finished: Bool) in
segmentControl.userInteractionEnabled = true
}
}
Bien que cela évite les commutations rapides (que vous pouvez voir comme un inconvénient), le seul autre moyen que je connaisse pour résoudre ce problème consiste à supprimer complètement les animations.
Vérifiez les contraintes de configuration et d'autolayout sur la vue de la pile et les sous-vues, en particulier le contrôle segmenté.
Le contrôle segmenté complique la configuration de la vue pile. Je supprime donc le contrôle segmenté de la vue pile et définit ses contraintes par rapport à la vue principale.
Avec le contrôle segmenté hors de la vue de pile, il est relativement simple de configurer la vue de pile pour que votre code fonctionne correctement.
Réinitialisez les contraintes sur la vue de pile afin qu'elle soit positionnée sous le contrôle segmenté et couvre le reste de la vue d'ensemble. Dans l'inspecteur d'attributs, définissez Alignement sur Remplissage, Distribution sur Remplir de manière égale et Mode contenu sur Echelle à remplir.
Supprimez les contraintes sur les vues secondaires et définissez leur mode de contenu sur Echelle pour le remplissage.
Ajustez l’indexation sur arrangéSubviews dans votre code et cela devrait fonctionner automatiquement.