Redimensionner les éléments enfants lorsque le frame de la UIView parente est modifiée avec une a

Bonjour à  tous !  :)


 


Je rencontre actuellement une petite difficulté. :(  Je souhaiterais que les frames des views contenues dans une View soient modifiés progressivement lorsque cette dernière connait un changement de proportion.


 


J'ai testé en implémentant la méthode layoutSubviews mais je n'accède qu'à  la valeur du frame finale et non aux valeurs intermédiaires. J'ai également ajouté une observation sur le changement du frame mais je n'ai rien de mieux.   :o


 


Comment puis-je procéder pour obtenir une animation cohérente ?  :)


 


Pour information, je crée ma vue via du code Swift. Je n'utilise pas xib.


 


Voici un code d'exemple qui illustre ma demande. Dans le cas ci-dessous, je souhaiterais que la largeur de titleView soit modifiée en temps réel lorsque la largeur de MyView est modifiée via une animation.



public class MyView: UIView {
private lazy var titleView: UIView = {
let x = CGFloat(0.0)
let y = CGFloat(0.0)
let width = self.frame.width
let height = CGFloat(20.0)
return UIView(frame: CGRect(x: x, y: y, width: width, height: height)
}()

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

public override init(frame: CGRect) {
super.init(frame: frame)
setup()
}

private func setup() {
self.addSubview(titleView)
}


public override func layoutSubviews() {
...
}
}

public class View: UIView {
private lazy var myView: MyView = {
let x = CGFloat(0.0)
let y = CGFloat(0.0)
let width = CGFloat(600.0)
let height = CGFloat(300.0)
return UIView(frame: CGRect(x: x, y: y, width: width, height: height)
}()

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

public override init(frame: CGRect) {
super.init(frame: frame)
setup()
}

private func setup() {
self.addSubview(myView)
}

public func animate() {
UIView.animate(withDuration: 0.5, delay: 0.0, option: .curveEaseInOut, animation: {
self.myView.frame = CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(300.0), height: CGFloat(300.0))
})
}
}

 Je viens d'écrire le code ci-dessous sur le forum mais je ne sais pas si il compile... ::)   L'idée est que vous ayez un aperçu de ma demande.  ;)


 


Merci pour vos réponses !  ;)


Mots clés:

Réponses

  • Pour information, je crée ma vue via du code Swift. Je n'utilise pas xib.

     



    Hérétique !  >:)


    Cela devrais fonctionner normalement si la vue était définie par rapport à  son contenant avec des contraintes Storyboard.


     


    T'as essayé de redéfinir ta vue avec drawRect ?



  • Hérétique !  >:)




     


    :P  :P  :P


     




    Cela devrais fonctionner normalement si la vue était définie par rapport à  son contenant avec des contraintes Storyboard.




     


    Je ne dis pas que "ça ne fonctionne pas". Ma vue enfant est bien redimensionnée mais pas de façon progressive en fonction de l'animation. Tu vois le truc ?


     




    T'as essayé de redéfinir ta vue avec drawRect ?




     


    Non, tu peux m'en dire plus ?  :)


  • Je ne dis pas que "ça ne fonctionne pas". Ma vue enfant est bien redimensionnée mais pas de façon progressive en fonction de l'animation. Tu vois le truc ?


     


     



    Oui, je vois très bien. Et je maintient que la vue enfant devrais (normalement) être redimensionnée progressivement pendant l'animation, si elle était définie par des contraintes par rapport à  son parent.


     



     


     


    Non, tu peux m'en dire plus ? 

     


    Je tape un petit code et je te donne ça .. demain ! Il se fait tard, là .



  • [...] si elle était définie par des contraintes par rapport à  son parent.




     


    Via des NSLayoutConstraint ?

  • DrakenDraken Membre
    août 2017 modifié #6

    Bon, je sèche. je pensais y arriver facilement et .. euh .. Cela ne fonctionne pas avec drawRect, ni avec les contraintes Storyboard. Je dois me tromper dans un paramètre d'animation. Affaire à  suivre ..


     


    EDIT : J'ai trouvé. Cela m'agaçait tellement que je n'arrivais pas à  dormir.


     



     


     


    Via des NSLayoutConstraint ?

     


    Oui


     


    Code et explication demain. Les NSLayoutConstraint sont construites avec Storyboard mais on peut le faire en code aussi (à  condition d'aimer souffrir !).

  • DrakenDraken Membre
    août 2017 modifié #7

    Bon, je ne vais pas t'infliger les NSLayoutConstraints. J'ai trouvé comment faire avec les vieilles méthodes de grand-maman.


     


    Ton problème venait du réglage de l'animation. Il faut ajouter .layoutSubviews dans les options pour forcer iOS à  recalculer les subviews pendant l'animation. J'ai aussi ajouté .allowAnimatedContent par prudence, même si ce n'est pas vraiment nécessaire dans ce cas précis.


     


    Pour tester, j'ai tapé un petit exemple avec une sous-vue dont la taille est égale à  son parent, moins une marge de 5 points



    import UIKit

    class UnParent: UIView {

    var enfant:UIView?

    override init(frame:CGRect) {
    super.init(frame:frame)
    enfant = UIView(frame: CGRect(x: 5, y: 5,
    width: frame.width-10,
    height: frame.height-10))
    if let enfantOk = enfant {
    self.addSubview(enfantOk)
    }

    }

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

    override func layoutSubviews() {
    let rect = self.frame
    enfant?.frame = CGRect(x: 5, y: 5,
    width: rect.width-10,
    height: rect.height-10)
    }
    }


    class ViewController: UIViewController {

    var monParent:UnParent?

    override func viewDidLoad() {
    super.viewDidLoad()

    let rectOrigine = CGRect(x: 0, y: 0, width: 100, height: 100)
    monParent = UnParent(frame: rectOrigine)
    // Ma religion m'interdisant d'utiliser !,
    // j'utilise un if let pour vérifier la validité de monParent
    if let monParent = monParent {
    self.view.addSubview(monParent)
    }

    // Couleurs
    monParent?.backgroundColor = UIColor.red
    monParent?.enfant?.backgroundColor = UIColor.cyan

    // Animations
    // L'option .layoutSubviews indique qu'il faut recalculer
    // la position des subviews pendant l'animation
    let rectDestination = CGRect(x: 0, y: 0, width: 300, height: 200)
    UIView.animate(withDuration: 5,
    delay: 0,
    options: [.curveEaseInOut, .allowAnimatedContent, .layoutSubviews],
    animations: { self.monParent?.frame = rectDestination },
    completion: nil)
    }

    }


  • Merci Draken ! 


     


    Et du coup ta solution fonctionne sans utiliser de NSLayoutConstraint  ?  :)




  •  


    Et du coup ta solution fonctionne sans utiliser de NSLayoutConstraint  ?  :)




    Tu vois un NSLayoutConstraint dans le programme d'exemple ?



  • Tu vois un NSLayoutConstraint dans le programme d'exemple ?




     


     


    Arf... Désolé, je n'avais pas pris le temps de bien lire ton post précédent...  o:)


     


    En te basant sur ton exemple, tu aurais la possibilité de faire une version en utilisant les NSLayoutConstraint ? C'est pour voir la différence entre les deux solutions... 


     


    Merci en tout cas pour ton retour.  :)

  • DrakenDraken Membre
    août 2017 modifié #11


    En te basant sur ton exemple, tu aurais la possibilité de faire une version en utilisant les NSLayoutConstraint ? C'est pour voir la différence entre les deux solutions... 


     




     


    Tu veux dire la solution que j'ai testé cette nuit ?


     


    Première étape :


    J'ai créé deux vue sous Storyboard, avec des contraintes liant le parent (MonParent) à  son enfant (enfant).


    J'ai ensuite tracé un IBOulet pour accéder à  MonParent dans le corps du code.


     


    Sa taille est définie par deux contraintes : 


     


    HauteurParent : MonParent.Height = 100 ([objet MonParent] [Relation Equal] [Constant = 100]


    LargeurParent : MonParent.Width = 100


     


    Normalement les contraintes sont fixes, mais on peut définir des IBOulets pour les manipuler avec le code. C'est ce que j'ai fait en créant les IBOutlets HauteurParent et LargeurParent.


     


    Seconde étape :


     


    Pour réaliser l'animation dans le code, j'ai défini les nouvelles valeurs avant l'animation et animer la mise à  jour du layout.



    class ViewController: UIViewController {

    @IBOutlet weak var monParent: UIView!
    @IBOutlet weak var largeurParent: NSLayoutConstraint!
    @IBOutlet weak var hauteurParent: NSLayoutConstraint!

    override func viewDidLoad() {
    super.viewDidLoad()

    // Paramétres de destination
    self.hauteurParent.constant = 300.0
    self.largeurParent.constant = 200.0
    UIView.animate(withDuration: 5,
    delay: 0,
    options: [.curveEaseInOut],
    animations: { self.monParent.layoutIfNeeded() },
    completion: nil)

    }
    }


    J'aurais pu définir les contraintes en code, mais c'est une galère, alors que cela ne prend que quelques instants avec Storyboard.




  • J'aurais pu définir les contraintes en code, mais c'est une galère, alors que cela ne prend que quelques instants avec Storyboard.




     


    Du coup, le code ci-dessus resterait inchangé mise à  par le fait de créer les contraintes à  la mano ?


     


    Si je spécifie via du code que l'élément enfant à  des contraintes de 5 pts par rapport aux bords de la vue parente (imagine un rectangle dans ton rectangle comme dans ton exemple). Si je modifie le frame de la vue parente via l'animation suivante :



    public func animate() {
    UIView.animate(withDuration: 0.5, delay: 0.0, option: .curveEaseInOut, animation: {
    self.myView.frame = CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(300.0), height: CGFloat(300.0))
    })
    }

    La vue fille se verra également redimensionnée en respectant les contraintes ?

  • DrakenDraken Membre
    août 2017 modifié #13

     


     


    Du coup, le code ci-dessus resterait inchangé mise à  par le fait de créer les contraintes à  la mano ?

     


    Je n'ai pas essayé, mais normalement oui. Je n'ai créé des contraintes par code, qu'une seule fois, dans le cadre d'un exercice du MOOC du professeur Kordon. C'était long, compliqué et galère. Définir une marge de 5 pixels avec Storyboard se fait en moins de 20 secondes. 


     


    Si la programmation par code des contraintes t'intéresse tu devrais allez voir les vidéos (en français) de F. Kordon sur le sujet, partie 4 de la section 6 de ces cours. Le lien est en rouge dans ma signature. 


     




    Si je spécifie via du code que l'élément enfant à  des contraintes de 5 pts par rapport aux bords de la vue parente (imagine un rectangle dans ton rectangle comme dans ton exemple). Si je modifie le frame de la vue parente via l'animation suivante :



    public func animate() {
    UIView.animate(withDuration: 0.5, delay: 0.0, option: .curveEaseInOut, animation: {
    self.myView.frame = CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(300.0), height: CGFloat(300.0))
    })
    }

    La vue fille se verra également redimensionnée en respectant les contraintes ?




     


    C'est ce que j'ai essayé en premier, mais cela ne fonctionne pas. C'est pourquoi j'ai réalisé l'animation en ajoutant des contraintes de taille pour la vue parente.


  • JérémyJérémy Membre
    août 2017 modifié #14

    ça marche !  ;)


     


    Pour récapituler :


       1 - Créer le la vue parente en utilisant des contraintes pour les valeurs width et height


       2 - Créer la vue enfant let v = UIView(frame : CGRect(x: x, y: y, width: width, height: height))


       3 - Ajouter mes contraintes pour que les proportions de ma vue enfant soit définie en fonction de ma vue parente


     


    Lors du point 2, si je sette width et height à  CGFloat(0.0), les contraintes doivent correctement ajuster la vue fille, non ?  :)


  • DrakenDraken Membre
    août 2017 modifié #15


     


    Pour récapituler :


       1 - Créer le la vue parente en utilisant des contraintes pour les valeurs width et height


       2 - Créer la vue enfant let v = UIView(frame : CGRect(x: x, y: y, width: width, height: height))


       3 - Ajouter mes contraintes pour que les proportions de ma vue enfant soit définie en fonction de ma vue parente


     




    Au point 2, tu doit créer la vue enfant sans lui donner de taille précise. Ce sont les contraintes qui vont calculer automatiquement la frame.


     




     


    Lors du point 2, si je sette width et height à  CGFloat(0.0), les contraintes doivent correctement ajuster la vue fille, non ?  :)




    Oui, si tu agit sur les contraintes définissant la taille de la vue parente, les contraintes de la vue fille la forcent à  s'adapter. C'est ce que j'ai fait dans mon code :



    class ViewController: UIViewController {

    @IBOutlet weak var monParent: UIView!
    @IBOutlet weak var largeurParent: NSLayoutConstraint!
    @IBOutlet weak var hauteurParent: NSLayoutConstraint!

    override func viewDidLoad() {
    super.viewDidLoad()

    // Paramétres de destination
    self.hauteurParent.constant = 300.0
    self.largeurParent.constant = 200.0
    UIView.animate(withDuration: 5,
    delay: 0,
    options: [.curveEaseInOut],
    animations: { self.monParent.layoutIfNeeded() },
    completion: nil)

    }
    }

  • JérémyJérémy Membre
    août 2017 modifié #16



    public func animate() {
    UIView.animate(withDuration: 0.5, delay: 0.0, option: .curveEaseInOut, animation: {
    self.myView.frame = CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(300.0), height: CGFloat(300.0))
    })

    C'est ce que j'ai essayé en premier, mais cela ne fonctionne pas. C'est pourquoi j'ai réalisé l'animation en ajoutant des contraintes de taille pour la vue parente.




     


    Dans le cas ci-dessus, je me demande si en ajoutant dans les options un paramètre (si il existe) qui force un recalcule en fonction des contraintes, je pourrais retomber sur mes pattes...  :)




  • Dans le cas ci-dessus, je me demande si en ajoutant dans les options un paramètre (si il existe) qui force un recalcule en fonction des contraintes, je pourrais retomber sur mes pattes...  :)




     


    Elle est bien cachée, si elle existe :


     


    https://developer.apple.com/documentation/uikit/uiviewanimationoptions

  • JérémyJérémy Membre
    août 2017 modifié #18

    D'accord ! Merci bien ! 


     


    Je suis en train de faire des tests par rapport à  ce que tu m'as indiqué histoire d'apprendre à  utiliser les contraintes.  :)



    public lazy var titleViewHeightConstraint: NSLayoutConstraint = {
    return NSLayoutConstraint(item: self.titleView,
    attribute: .height,
    relatedBy: .equal,
    toItem: nil,
    attribute: .notAnAttribute,
    multiplier: 1,
    constant: CGFloat(20.0))
    }()

    [...]

    func updateFrame() {
    self.titleViewHeightConstraint = CGFloat(30.0)
    // ???
    //self.updateConstraints()
    //self.updateConstraintsIfNeeded()
    //self.layoutIfNeeded()

    }

    Dans la méthode updateFrame, je souhaite mettre à  jour la hauteur de ma vue. Seulement, une fois la nouvelle valeur settée, la mise à  jour n'apparait pas à  l'écran. Quelle fonction faut il appliquer pour que d'un point de vue rendu graphique, la mise à  jour soit effective ?  :)


  • DrakenDraken Membre
    août 2017 modifié #19

    Euh .. je ne sais plus. C'est trop compliqué pour moi les AutoLayout par code, alors que c'est si simple avec Storyboard. Tu devrais vraiment regarder les vidéos du prof Kordon sur l'utilisation des Autolayout par code. Il donne des exemples. J'en avais vraiment galèré à  l'époque de son MOOC (Mars 2017).


  • Je viens de trouver. Je n'avais pas setté à  false la valeur de la propriété translatesAutoresizingMaskIntoConstraints. Maintenant ça fonctionne parfaitement.  :)



    self.titleView.translatesAutoresizingMaskIntoConstraints = false

    Merci Draken pour ton aide ! Je pousserai les tests dans les jours à  venir. 




  • Je viens de trouver. Je n'avais pas setté à  false la valeur de la propriété translatesAutoresizingMaskIntoConstraints. Maintenant ça fonctionne parfaitement.  :)



    self.titleView.translatesAutoresizingMaskIntoConstraints = false



     


    C'est clair qu'en n'activant pas l'AutoLayout cela ne risquait pas de fonctionner. J'avais été surpris à  l'époque de voir qu'il fallait mettre la valeur false pour l'activer. Storyboard gère automatiquement tous ces fichus paramètres.

  • Merci bien Draken !  :)


     


    J'arrive à  créer le comportement que je voulais. Pour faire simple, j'ai repris ta solution mais en codant les contraintes. Si tu maà®trises le procédé sur xib, tu arrives à  t'en sortir. Par contre ce n'est pas du tout intuitif, tu n'as pas d'infobulle pour t'avertir qu'il y a un conflit entre tes NSLayoutConstraint...


     


    Par contre (chose qui est peut être étrange), l'animation fonctionne bien (les éléments suivent bien également) avec le code ci-dessous:



    public func animate() {
    UIView.animate(withDuration: 0.5, delay: 0.0, option: [.curveEaseInOut,.layoutSubviews], animation: {
    self.myView.frame = CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(300.0), height: CGFloat(300.0))
    })
    }
  • Good !


     


    Bon travail.


     


     




     


    Par contre (chose qui est peut être étrange), l'animation fonctionne bien (les éléments suivent bien également) avec le code ci-dessous:



    public func animate() {
    UIView.animate(withDuration: 0.5, delay: 0.0, option: [.curveEaseInOut,.layoutSubviews], animation: {
    self.myView.frame = CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(300.0), height: CGFloat(300.0))
    })
    }



     


    Il doit y avoir une différence subtile entre l'utilisation de Storyboard et des Autolayout par code. Ou je me suis trompé dans mes tests, ce qui n'aurais rien d'étonnant. Mais la question m'intéresse, je m'y replongerais quand j'aurais un peu de temps libre. 

  • J'avais zappé...


     




    J'avais été surpris à  l'époque de voir qu'il fallait mettre la valeur false pour l'activer. Storyboard gère automatiquement tous ces fichus paramètres.




     


    La même pour moi !  xd 


     


    Par défaut, le storyboard positionne la variable à  false alors que via du code c'est la valeur true qui sera positionnée...


     


    Les animations, je les appliquais directement sur les frames. Mais je vais jouer avec les NSLayoutContraints, ça me semble pus simple.

Connectez-vous ou Inscrivez-vous pour répondre.