Connaà®tre la position d'une vue pendant une animation

DrakenDraken Membre
juillet 2017 modifié dans API UIKit #1

En jouant avec UIView.animate() je me suis aperçu d'un truc curieux, il est impossible de connaà®tre la position d'une vue pendant une animation de mouvement.


 


Pour tester ça, j'ai écrit une mini application déplaçant une barre verticale de droite à  gauche.



import UIKit

class Rectangle : UIView {

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print ("touchesBegan")
}


}

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

self.view.tag = 1

let sizeEcran = self.view.bounds.size

/*
let vueRouge = UIView(frame: CGRect(x: 0,
y: 0,
width: sizeEcran.width*0.4,
height: sizeEcran.height))
*/
let vueRouge = Rectangle(frame: CGRect(x: 0,
y: 0,
width: sizeEcran.width*0.4,
height: sizeEcran.height))

vueRouge.backgroundColor = UIColor.red
self.view.addSubview(vueRouge)
vueRouge.tag = 42

let pointDestination = CGPoint(x: sizeEcran.width-vueRouge.bounds.width*0.5,
y: sizeEcran.height*0.5)

UIView.animate(withDuration: 2.0,
delay: 0,
options: [.repeat,
.autoreverse,
.curveLinear,
.allowUserInteraction],
animations: { vueRouge.center = pointDestination },
completion: nil)
}


@IBAction func actionTap(_ sender: UITapGestureRecognizer) {
let pos = sender.location(in: self.view)

/*
if let vueDetection = self.view.hitTest(pos, with: nil) {
print ("Tag : ", vueDetection.tag)
}
*/

if let test = detectionVue(vue: self.view, position: pos) {
print ("Tag : ", test.tag)
}
}


func detectionVue(vue:UIView, position:CGPoint) -> UIView? {
for subView in vue.subviews {
if !subView.isHidden {
print ("subview (tag ",subView.tag, ") : ", subView.frame)
}
}
return nil
}
}

Une gesture définie dans le Storyboard appelle la fonction actionTap() quand l'utilisateur touche l'écran. 


 


Normalement les interactions utilisateurs sont désactivés pendant les animations, mais j'ai utilisé le paramètre .allowUserInteraction pour les réactiver. J'ai d'abord utiliser hitTest(), puis j'ai écrit une fonction spécialisée allant lire les subviews de l'écran pour tester individuellement chaque vue. Ensuite j'ai tenté de sous-classer la vue. Le code en haut est un peu bordélique à  cause de tous ces essais.


 


Conclusion : pendant une animation de mouvement, le système considère que la vue est TOUJOURS A SA POSITION FINALE. Quelque soit la position visible de la barre pendant l'animation, pour UIKit elle est à  droite de l'écran


 


Comme vous pouvez avec les relevés de position suivants, UIKit ne semble pas savoir que la barre se balade sur l'écran, alors que j'ai lu la position à  différents moments de l'animation.


 



 


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)


subview (tag  42 ) :  (192.0, 0.0, 128.0, 568.0)



 

Je ne sais pas comment UIKit se débrouille pour gérer les animations, mais en tout cas il cache bien l'information de position. La vue semble toujours au même endroit, le layer aussi. 


 


Quelqu'un a une suggestion sur la manière de lire la position de l'animation à  un moment t ?

Réponses

  • DrakenDraken Membre
    juillet 2017 modifié #3

    YEEEEES ça marche !!!  o:)


     


    Ceci dit, je ne comprend pas pourquoi view.layer.bounds donne une information (très) différente de view.layer.presentation().frame.

  • Oui, c'est sacrément intéressant. Y compris la possibilité de définir des trajectoires d'animation avec des courbes complexes. Merci nounours !

  • DrakenDraken Membre
    juillet 2017 modifié #7

    Eh bien, je vais avoir de quoi lire ce soir .. 


    C'est sympa de voir qu'iOS 10 intègre une nouvelle API d'animation (UIViewPropertyAnimator) pour pallier aux limitations de l'ancien système, UIView.animate() apparu avec iOS 4.0, qui commençait à  marquer son âge. Il était temps d'avoir un peu de sang neuf.

  • DrakenDraken Membre
    juillet 2017 modifié #8

    La nouvelle API a une propriété pour activer le hitTesting. Sympa ..



    var isManualHitTestingEnabled: Bool { get set }


    La doc complète ici : https://developer.apple.com/documentation/uikit/uiviewpropertyanimator


     


    On peut aussi mettre une animation sur pause. Pratique ..

  • DrakenDraken Membre
    juillet 2017 modifié #9

    Voici le programme corrigé, avec détection d'une vue avec une animation de mouvement, basé sur la nouvelle API UIViewPropertyAnimator. A la différence que l'animation de la barre rouge n'est pas permanente. Je n'ai pas trouvé le moyen de répéter une animation à  l'infini. Le paramètre .repeat de l'ancienne API ne fonctionne pas. L'animation est exécutée 1000 fois, ce qui est suffisant pour les tests.



    import UIKit

    class ViewController: UIViewController {

    let vueRouge = UIView()

    override func viewDidLoad() {
    super.viewDidLoad()

    self.view.tag = 1

    let sizeEcran = self.view.bounds.size

    vueRouge.frame = CGRect(x: 0,
    y: 0,
    width: sizeEcran.width*0.4,
    height: sizeEcran.height)

    vueRouge.backgroundColor = UIColor.red
    self.view.addSubview(vueRouge)
    vueRouge.tag = 42

    let pointDestination = CGPoint(x: sizeEcran.width-vueRouge.bounds.width*0.5,
    y: sizeEcran.height*0.5)

    // Création animation
    let animation = UIViewPropertyAnimator(
    duration: 2.0,
    curve: .linear,
    animations: {
    UIView.setAnimationRepeatCount(1000)
    UIView.setAnimationRepeatAutoreverses(true)
    self.vueRouge.center = pointDestination } )

    animation.startAnimation()
    }


    @IBAction func actionTap(_ sender: UITapGestureRecognizer) {
    let pos = sender.location(in: self.view)

    if let vueDetection = self.view.hitTest(pos, with: nil) {
    print ("Tag : ", vueDetection.tag)
    }

    }

    }



    Le tag 1 est associé à  l'écran vide, la valeur 42 à  la barre en mouvement !


     



     


    Tag :  1


    Tag :  42


    Tag :  1


    Tag :  42


    Tag :  1


    Tag :  1


    Tag :  42


    Tag :  1


    Tag :  42


    Tag :  42


    Tag :  42


    Tag :  42


    Tag :  1


    Tag :  1


    Tag :  42


     


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