135 votes

La modification du point d'ancrage de mon CALayer déplace la vue

Je veux modifier le anchorPoint mais en gardant la vue au même endroit. J'ai essayé NSLog -ing self.layer.position y self.center et ils restent tous deux identiques, quelles que soient les modifications apportées au point d'ancrage. Pourtant, ma vue bouge !

Des conseils sur la manière de procéder ?

self.layer.anchorPoint = CGPointMake(0.5, 0.5);
NSLog(@"center point: %f %f", self.layer.position.x, self.layer.position.y);
self.layer.anchorPoint = CGPointMake(1, 1);
NSLog(@"center point: %f %f", self.layer.position.x, self.layer.position.y);

La sortie est :

2009-12-27 20:43:24.161 Type[11289:207] center point: 272.500000 242.500000
2009-12-27 20:43:24.162 Type[11289:207] center point: 272.500000 242.500000

210voto

Magnus Points 1031

J'ai eu le même problème. La solution de Brad Larson fonctionne très bien, même lorsque la vue est tournée. Voici sa solution traduite en code.

-(void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view
{
    CGPoint newPoint = CGPointMake(view.bounds.size.width * anchorPoint.x, 
                                   view.bounds.size.height * anchorPoint.y);
    CGPoint oldPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x, 
                                   view.bounds.size.height * view.layer.anchorPoint.y);

    newPoint = CGPointApplyAffineTransform(newPoint, view.transform);
    oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform);

    CGPoint position = view.layer.position;

    position.x -= oldPoint.x;
    position.x += newPoint.x;

    position.y -= oldPoint.y;
    position.y += newPoint.y;

    view.layer.position = position;
    view.layer.anchorPoint = anchorPoint;
}

Et l'équivalent rapide :

func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPointMake(view.bounds.size.width * anchorPoint.x, view.bounds.size.height * anchorPoint.y)
    var oldPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x, view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = CGPointApplyAffineTransform(newPoint, view.transform)
    oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform)

    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}

SWIFT 4.x

func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) {
    var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x,
                           y: view.bounds.size.height * anchorPoint.y)

    var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x,
                           y: view.bounds.size.height * view.layer.anchorPoint.y)

    newPoint = newPoint.applying(view.transform)
    oldPoint = oldPoint.applying(view.transform)

    var position = view.layer.position
    position.x -= oldPoint.x
    position.x += newPoint.x

    position.y -= oldPoint.y
    position.y += newPoint.y

    view.layer.position = position
    view.layer.anchorPoint = anchorPoint
}

2 votes

Parfait et tout à fait logique une fois que j'ai pu voir votre exemple ici. Merci pour cela !

2 votes

J'utilise ce code dans mes méthodes awakeFromNib/initWithFrame, mais cela ne fonctionne toujours pas, dois-je mettre à jour l'affichage ? edit : setNeedsDisplay ne fonctionne pas

2 votes

Pourquoi utilisez-vous view.bounds ? Ne devriez-vous pas utiliser layer.bounds ?

142voto

Brad Larson Points 122629

Le site Géométrie et transformations des couches du Guide de programmation de l'animation de base explique la relation entre les propriétés position et anchorPoint d'un CALayer. Fondamentalement, la position d'un calque est spécifiée en fonction de l'emplacement du point d'ancrage du calque. Par défaut, le point d'ancrage d'un calque est (0,5, 0,5), ce qui correspond au centre du calque. Lorsque vous définissez la position d'une couche, vous définissez également l'emplacement du centre de la couche dans le système de coordonnées de sa supercouche.

Comme la position est relative au point d'ancrage du calque, le fait de modifier ce point d'ancrage tout en conservant la même position déplace le calque. Pour éviter ce mouvement, vous devez ajuster la position du calque pour tenir compte du nouveau point d'ancrage. Une façon de procéder consiste à saisir les limites du calque, à multiplier la largeur et la hauteur des limites par les valeurs normalisées de l'ancien et du nouveau point d'ancrage, à prendre la différence entre les deux points d'ancrage et à appliquer cette différence à la position du calque.

Vous pourriez même être en mesure de tenir compte de la rotation de cette manière en utilisant CGPointApplyAffineTransform() avec la CGAffineTransform de votre UIView.

4 votes

Consultez la fonction d'exemple de Magnus pour voir comment la réponse de Brad peut être implémentée en Objective-C.

0 votes

Merci mais je ne comprends pas comment le anchorPoint y position sont liés entre eux ?

7 votes

@ss1271 - Comme je le décris ci-dessus, le point d'ancrage d'une couche est la base de son système de coordonnées. S'il est défini sur (0,5, 0,5), lorsque vous définissez la position d'un calque, vous définissez l'emplacement de son centre dans le système de coordonnées de son supercalque. Si vous définissez le point d'ancrage sur (0,0, 0,5), la position déterminera l'emplacement du centre de son bord gauche. Encore une fois, Apple propose de belles images dans la documentation mentionnée ci-dessus (dont j'ai mis à jour la référence).

47voto

Kenny Winker Points 6171

La clé pour résoudre ce problème était d'utiliser la propriété frame, qui est bizarrement la seule chose qui change.

Swift 2

let oldFrame = self.frame
self.layer.anchorPoint = CGPointMake(1, 1)
self.frame = oldFrame

Swift 3

let oldFrame = self.frame
self.layer.anchorPoint = CGPoint(x: 1, y: 1)
self.frame = oldFrame

Puis je fais mon redimensionnement, où il est mis à l'échelle à partir du point d'ancrage. Ensuite, je dois restaurer l'ancien point d'ancrage ;

Swift 2

let oldFrame = self.frame
self.layer.anchorPoint = CGPointMake(0.5,0.5)
self.frame = oldFrame

Swift 3

let oldFrame = self.frame
self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.frame = oldFrame

EDIT : cela s'arrête si la vue est tournée, car la propriété frame est indéfinie si une CGAffineTransform a été appliquée.

10 votes

Je voulais juste ajouter pourquoi cela fonctionne et semble être la méthode la plus simple : selon la documentation, la propriété frame est une propriété "composée" : "La valeur de frame est dérivée des propriétés bounds, anchorPoint et position." C'est aussi pourquoi la propriété "frame" n'est pas animable.

2 votes

C'est honnêtement l'approche que je préfère. Si vous n'essayez pas de manipuler les limites et la position indépendamment ou d'animer l'une ou l'autre, le fait de capturer l'image avant de changer le point d'ancrage est généralement suffisant. Aucun calcul n'est nécessaire.

28voto

2cupsOfTech Points 1541

Pour moi, la compréhension position y anchorPoint a été plus facile lorsque j'ai commencé à la comparer avec ma compréhension de frame.origin dans UIView. Une UIView avec frame.origin = (20,30) signifie que l'UIView se trouve à 20 points de la gauche et à 30 points du haut de sa vue parente. Cette distance est calculée à partir de quel point d'une UIView ? Elle est calculée à partir du coin supérieur gauche d'une UIView.

En couche anchorPoint marque le point (sous forme normalisée, c'est-à-dire de 0 à 1) à partir duquel cette distance est calculée. Ainsi, par exemple, layer.position = (20, 30) signifie que la couche anchorPoint est à 20 points de la gauche et à 30 points du haut de sa couche mère. Par défaut, le point d'ancrage d'un calque est (0,5, 0,5), de sorte que le point de calcul de la distance se trouve en plein centre du calque. La figure suivante va m'aider à clarifier mon point de vue :

enter image description here

anchorPoint est également le point autour duquel la rotation se produira si vous appliquez une transformation au calque.

1 votes

S'il vous plaît, pouvez-vous me dire comment je peux résoudre ce saut de point d'ancrage dont la UIView est définie en utilisant la mise en page automatique, ce qui signifie qu'avec la mise en page automatique, nous ne sommes pas censés obtenir ou définir les propriétés des cadres et des limites.

18voto

user945711 Points 107

Il y a une solution si simple. C'est basé sur la réponse de Kenny. Mais au lieu d'appliquer l'ancien cadre, utilisez son origine et le nouveau pour calculer la translation, puis appliquez cette translation au centre. Cela fonctionne aussi avec les vues tournées ! Voici le code, beaucoup plus simple que les autres solutions :

func setAnchorPoint(anchorPoint: CGPoint, view: UIView) {
   let oldOrigin = view.frame.origin
   view.layer.anchorPoint = anchorPoint
   let newOrigin = view.frame.origin

   let translation = CGPoint(x: newOrigin.x - oldOrigin.x, y: newOrigin.y - oldOrigin.y)

   view.center = CGPoint(x: view.center.x - translation.x, y: view.center.y - translation.y)
}

2 votes

Jolie petite méthode... elle fonctionne bien avec quelques petites corrections : Ligne 3 : P majuscule dans anchorPoint. Ligne 8 : TRANS.Y = NEW - OLD

2 votes

Je suis toujours dans la confusion, comment puis-je l'utiliser ? Je suis confronté au même problème en ce moment pour faire tourner ma vue avec uigesturerotation. Je me demande où je dois appeler cette méthode. Si je l'appelle dans le gestionnaire de reconnaissance des gestes. Cela fait disparaître la vue.

3 votes

Cette réponse est si douce que j'ai maintenant du diabète.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X